+1
-1
README.md
+1
-1
README.md
···
256
256
257
257
### Other
258
258
259
-
- [ ] `com.atproto.label.queryLabels`
259
+
- [x] `com.atproto.label.queryLabels`
260
260
- [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS)
261
261
- [x] `app.bsky.actor.getPreferences`
262
262
- [x] `app.bsky.actor.putPreferences`
+6
cmd/cocoon/main.go
+6
cmd/cocoon/main.go
···
79
79
Name: "admin-password",
80
80
EnvVars: []string{"COCOON_ADMIN_PASSWORD"},
81
81
},
82
+
&cli.BoolFlag{
83
+
Name: "require-invite",
84
+
EnvVars: []string{"COCOON_REQUIRE_INVITE"},
85
+
Value: true,
86
+
},
82
87
&cli.StringFlag{
83
88
Name: "smtp-user",
84
89
EnvVars: []string{"COCOON_SMTP_USER"},
···
185
190
Version: Version,
186
191
Relays: cmd.StringSlice("relays"),
187
192
AdminPassword: cmd.String("admin-password"),
193
+
RequireInvite: cmd.Bool("require-invite"),
188
194
SmtpUser: cmd.String("smtp-user"),
189
195
SmtpPass: cmd.String("smtp-pass"),
190
196
SmtpHost: cmd.String("smtp-host"),
+34
server/handle_label_query_labels.go
+34
server/handle_label_query_labels.go
···
1
+
package server
2
+
3
+
import (
4
+
"github.com/labstack/echo/v4"
5
+
)
6
+
7
+
type Label struct {
8
+
Ver *int `json:"ver,omitempty"`
9
+
Src string `json:"src"`
10
+
Uri string `json:"uri"`
11
+
Cid *string `json:"cid,omitempty"`
12
+
Val string `json:"val"`
13
+
Neg *bool `json:"neg,omitempty"`
14
+
Cts string `json:"cts"`
15
+
Exp *string `json:"exp,omitempty"`
16
+
Sig []byte `json:"sig,omitempty"`
17
+
}
18
+
19
+
type ComAtprotoLabelQueryLabelsResponse struct {
20
+
Cursor *string `json:"cursor,omitempty"`
21
+
Labels []Label `json:"labels"`
22
+
}
23
+
24
+
func (s *Server) handleLabelQueryLabels(e echo.Context) error {
25
+
svc := e.Request().Header.Get("atproto-proxy")
26
+
if svc != "" || s.config.FallbackProxy != "" {
27
+
return s.handleProxy(e)
28
+
}
29
+
30
+
return e.JSON(200, ComAtprotoLabelQueryLabelsResponse{
31
+
Cursor: nil,
32
+
Labels: []Label{},
33
+
})
34
+
}
+19
-11
server/handle_server_create_account.go
+19
-11
server/handle_server_create_account.go
···
25
25
Handle string `json:"handle" validate:"required,atproto-handle"`
26
26
Did *string `json:"did" validate:"atproto-did"`
27
27
Password string `json:"password" validate:"required"`
28
-
InviteCode string `json:"inviteCode" validate:"required"`
28
+
InviteCode string `json:"inviteCode" validate:"omitempty"`
29
29
}
30
30
31
31
type ComAtprotoServerCreateAccountResponse struct {
···
104
104
}
105
105
106
106
var ic models.InviteCode
107
-
if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil {
108
-
if err == gorm.ErrRecordNotFound {
107
+
if s.config.RequireInvite {
108
+
if strings.TrimSpace(request.InviteCode) == "" {
109
109
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
110
110
}
111
-
s.logger.Error("error getting invite code from db", "error", err)
112
-
return helpers.ServerError(e, nil)
113
-
}
114
111
115
-
if ic.RemainingUseCount < 1 {
116
-
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
112
+
if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil {
113
+
if err == gorm.ErrRecordNotFound {
114
+
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
115
+
}
116
+
s.logger.Error("error getting invite code from db", "error", err)
117
+
return helpers.ServerError(e, nil)
118
+
}
119
+
120
+
if ic.RemainingUseCount < 1 {
121
+
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
122
+
}
117
123
}
118
124
119
125
// see if the email is already taken
···
234
240
})
235
241
}
236
242
237
-
if err := s.db.Raw("UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil {
238
-
s.logger.Error("error decrementing use count", "error", err)
239
-
return helpers.ServerError(e, nil)
243
+
if s.config.RequireInvite {
244
+
if err := s.db.Raw("UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil {
245
+
s.logger.Error("error decrementing use count", "error", err)
246
+
return helpers.ServerError(e, nil)
247
+
}
240
248
}
241
249
242
250
sess, err := s.createSession(&urepo)
+1
-1
server/handle_server_describe_server.go
+1
-1
server/handle_server_describe_server.go
···
22
22
23
23
func (s *Server) handleDescribeServer(e echo.Context) error {
24
24
return e.JSON(200, ComAtprotoServerDescribeServerResponse{
25
-
InviteCodeRequired: true,
25
+
InviteCodeRequired: s.config.RequireInvite,
26
26
PhoneVerificationRequired: false,
27
27
AvailableUserDomains: []string{"." + s.config.Hostname}, // TODO: more
28
28
Links: ComAtprotoServerDescribeServerResponseLinks{
+33
server/handle_well_known.go
+33
server/handle_well_known.go
···
2
2
3
3
import (
4
4
"fmt"
5
+
"strings"
5
6
6
7
"github.com/Azure/go-autorest/autorest/to"
8
+
"github.com/haileyok/cocoon/internal/helpers"
7
9
"github.com/labstack/echo/v4"
10
+
"gorm.io/gorm"
8
11
)
9
12
10
13
var (
···
61
64
},
62
65
},
63
66
})
67
+
}
68
+
69
+
func (s *Server) handleAtprotoDid(e echo.Context) error {
70
+
host := e.Request().Host
71
+
if host == "" {
72
+
return helpers.InputError(e, to.StringPtr("Invalid handle."))
73
+
}
74
+
75
+
host = strings.Split(host, ":")[0]
76
+
host = strings.ToLower(strings.TrimSpace(host))
77
+
78
+
if host == s.config.Hostname {
79
+
return e.String(200, s.config.Did)
80
+
}
81
+
82
+
suffix := "." + s.config.Hostname
83
+
if !strings.HasSuffix(host, suffix) {
84
+
return e.NoContent(404)
85
+
}
86
+
87
+
actor, err := s.getActorByHandle(host)
88
+
if err != nil {
89
+
if err == gorm.ErrRecordNotFound {
90
+
return e.NoContent(404)
91
+
}
92
+
s.logger.Error("error looking up actor by handle", "error", err)
93
+
return helpers.ServerError(e, nil)
94
+
}
95
+
96
+
return e.String(200, actor.Did)
64
97
}
65
98
66
99
func (s *Server) handleOauthProtectedResource(e echo.Context) error {
+7
server/server.go
+7
server/server.go
···
102
102
ContactEmail string
103
103
Relays []string
104
104
AdminPassword string
105
+
RequireInvite bool
105
106
106
107
SmtpUser string
107
108
SmtpPass string
···
126
127
EnforcePeering bool
127
128
Relays []string
128
129
AdminPassword string
130
+
RequireInvite bool
129
131
SmtpEmail string
130
132
SmtpName string
131
133
BlockstoreVariant BlockstoreVariant
···
379
381
EnforcePeering: false,
380
382
Relays: args.Relays,
381
383
AdminPassword: args.AdminPassword,
384
+
RequireInvite: args.RequireInvite,
382
385
SmtpName: args.SmtpName,
383
386
SmtpEmail: args.SmtpEmail,
384
387
BlockstoreVariant: args.BlockstoreVariant,
···
442
445
s.echo.GET("/", s.handleRoot)
443
446
s.echo.GET("/xrpc/_health", s.handleHealth)
444
447
s.echo.GET("/.well-known/did.json", s.handleWellKnown)
448
+
s.echo.GET("/.well-known/atproto-did", s.handleAtprotoDid)
445
449
s.echo.GET("/.well-known/oauth-protected-resource", s.handleOauthProtectedResource)
446
450
s.echo.GET("/.well-known/oauth-authorization-server", s.handleOauthAuthorizationServer)
447
451
s.echo.GET("/robots.txt", s.handleRobots)
···
465
469
s.echo.GET("/xrpc/com.atproto.sync.subscribeRepos", s.handleSyncSubscribeRepos)
466
470
s.echo.GET("/xrpc/com.atproto.sync.listBlobs", s.handleSyncListBlobs)
467
471
s.echo.GET("/xrpc/com.atproto.sync.getBlob", s.handleSyncGetBlob)
472
+
473
+
// labels
474
+
s.echo.GET("/xrpc/com.atproto.label.queryLabels", s.handleLabelQueryLabels)
468
475
469
476
// account
470
477
s.echo.GET("/account", s.handleAccount)