tangled
alpha
login
or
join now
willdot.net
/
cocoon
forked from
hailey.at/cocoon
0
fork
atom
An atproto PDS written in Go
0
fork
atom
overview
issues
pulls
pipelines
refactor: combine admin into main cocoon binary
hailey.at
8 months ago
be005ba5
0d5a384c
+169
-188
2 changed files
expand all
collapse all
unified
split
cmd
admin
main.go
cocoon
main.go
-186
cmd/admin/main.go
···
1
1
-
package main
2
2
-
3
3
-
import (
4
4
-
"crypto/ecdsa"
5
5
-
"crypto/elliptic"
6
6
-
"crypto/rand"
7
7
-
"encoding/json"
8
8
-
"fmt"
9
9
-
"os"
10
10
-
"time"
11
11
-
12
12
-
"github.com/bluesky-social/indigo/atproto/crypto"
13
13
-
"github.com/bluesky-social/indigo/atproto/syntax"
14
14
-
"github.com/haileyok/cocoon/internal/helpers"
15
15
-
"github.com/lestrrat-go/jwx/v2/jwk"
16
16
-
"github.com/urfave/cli/v2"
17
17
-
"golang.org/x/crypto/bcrypt"
18
18
-
"gorm.io/driver/sqlite"
19
19
-
"gorm.io/gorm"
20
20
-
)
21
21
-
22
22
-
func main() {
23
23
-
app := cli.App{
24
24
-
Name: "admin",
25
25
-
Commands: cli.Commands{
26
26
-
runCreateRotationKey,
27
27
-
runCreatePrivateJwk,
28
28
-
runCreateInviteCode,
29
29
-
runResetPassword,
30
30
-
},
31
31
-
ErrWriter: os.Stdout,
32
32
-
}
33
33
-
34
34
-
app.Run(os.Args)
35
35
-
}
36
36
-
37
37
-
var runCreateRotationKey = &cli.Command{
38
38
-
Name: "create-rotation-key",
39
39
-
Usage: "creates a rotation key for your pds",
40
40
-
Flags: []cli.Flag{
41
41
-
&cli.StringFlag{
42
42
-
Name: "out",
43
43
-
Required: true,
44
44
-
Usage: "output file for your rotation key",
45
45
-
},
46
46
-
},
47
47
-
Action: func(cmd *cli.Context) error {
48
48
-
key, err := crypto.GeneratePrivateKeyK256()
49
49
-
if err != nil {
50
50
-
return err
51
51
-
}
52
52
-
53
53
-
bytes := key.Bytes()
54
54
-
55
55
-
if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil {
56
56
-
return err
57
57
-
}
58
58
-
59
59
-
return nil
60
60
-
},
61
61
-
}
62
62
-
63
63
-
var runCreatePrivateJwk = &cli.Command{
64
64
-
Name: "create-private-jwk",
65
65
-
Usage: "creates a private jwk for your pds",
66
66
-
Flags: []cli.Flag{
67
67
-
&cli.StringFlag{
68
68
-
Name: "out",
69
69
-
Required: true,
70
70
-
Usage: "output file for your jwk",
71
71
-
},
72
72
-
},
73
73
-
Action: func(cmd *cli.Context) error {
74
74
-
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
75
75
-
if err != nil {
76
76
-
return err
77
77
-
}
78
78
-
79
79
-
key, err := jwk.FromRaw(privKey)
80
80
-
if err != nil {
81
81
-
return err
82
82
-
}
83
83
-
84
84
-
kid := fmt.Sprintf("%d", time.Now().Unix())
85
85
-
86
86
-
if err := key.Set(jwk.KeyIDKey, kid); err != nil {
87
87
-
return err
88
88
-
}
89
89
-
90
90
-
b, err := json.Marshal(key)
91
91
-
if err != nil {
92
92
-
return err
93
93
-
}
94
94
-
95
95
-
if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil {
96
96
-
return err
97
97
-
}
98
98
-
99
99
-
return nil
100
100
-
},
101
101
-
}
102
102
-
103
103
-
var runCreateInviteCode = &cli.Command{
104
104
-
Name: "create-invite-code",
105
105
-
Usage: "creates an invite code",
106
106
-
Flags: []cli.Flag{
107
107
-
&cli.StringFlag{
108
108
-
Name: "for",
109
109
-
Usage: "optional did to assign the invite code to",
110
110
-
},
111
111
-
&cli.IntFlag{
112
112
-
Name: "uses",
113
113
-
Usage: "number of times the invite code can be used",
114
114
-
Value: 1,
115
115
-
},
116
116
-
},
117
117
-
Action: func(cmd *cli.Context) error {
118
118
-
db, err := newDb()
119
119
-
if err != nil {
120
120
-
return err
121
121
-
}
122
122
-
123
123
-
forDid := "did:plc:123"
124
124
-
if cmd.String("for") != "" {
125
125
-
did, err := syntax.ParseDID(cmd.String("for"))
126
126
-
if err != nil {
127
127
-
return err
128
128
-
}
129
129
-
130
130
-
forDid = did.String()
131
131
-
}
132
132
-
133
133
-
uses := cmd.Int("uses")
134
134
-
135
135
-
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8))
136
136
-
137
137
-
if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil {
138
138
-
return err
139
139
-
}
140
140
-
141
141
-
fmt.Printf("New invite code created with %d uses: %s\n", uses, code)
142
142
-
143
143
-
return nil
144
144
-
},
145
145
-
}
146
146
-
147
147
-
var runResetPassword = &cli.Command{
148
148
-
Name: "reset-password",
149
149
-
Usage: "resets a password",
150
150
-
Flags: []cli.Flag{
151
151
-
&cli.StringFlag{
152
152
-
Name: "did",
153
153
-
Usage: "did of the user who's password you want to reset",
154
154
-
},
155
155
-
},
156
156
-
Action: func(cmd *cli.Context) error {
157
157
-
db, err := newDb()
158
158
-
if err != nil {
159
159
-
return err
160
160
-
}
161
161
-
162
162
-
didStr := cmd.String("did")
163
163
-
did, err := syntax.ParseDID(didStr)
164
164
-
if err != nil {
165
165
-
return err
166
166
-
}
167
167
-
168
168
-
newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12))
169
169
-
hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10)
170
170
-
if err != nil {
171
171
-
return err
172
172
-
}
173
173
-
174
174
-
if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil {
175
175
-
return err
176
176
-
}
177
177
-
178
178
-
fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass)
179
179
-
180
180
-
return nil
181
181
-
},
182
182
-
}
183
183
-
184
184
-
func newDb() (*gorm.DB, error) {
185
185
-
return gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{})
186
186
-
}
+169
-2
cmd/cocoon/main.go
···
1
1
package main
2
2
3
3
import (
4
4
+
"crypto/ecdsa"
5
5
+
"crypto/elliptic"
6
6
+
"crypto/rand"
7
7
+
"encoding/json"
4
8
"fmt"
5
9
"os"
10
10
+
"time"
6
11
12
12
+
"github.com/bluesky-social/indigo/atproto/crypto"
13
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
14
+
"github.com/haileyok/cocoon/internal/helpers"
7
15
"github.com/haileyok/cocoon/server"
8
16
_ "github.com/joho/godotenv/autoload"
17
17
+
"github.com/lestrrat-go/jwx/v2/jwk"
9
18
"github.com/urfave/cli/v2"
19
19
+
"golang.org/x/crypto/bcrypt"
20
20
+
"gorm.io/driver/sqlite"
21
21
+
"gorm.io/gorm"
10
22
)
11
23
12
24
var Version = "dev"
···
121
133
},
122
134
},
123
135
Commands: []*cli.Command{
124
124
-
run,
136
136
+
runServe,
137
137
+
runCreateRotationKey,
138
138
+
runCreatePrivateJwk,
139
139
+
runCreateInviteCode,
140
140
+
runResetPassword,
125
141
},
126
142
ErrWriter: os.Stdout,
127
143
Version: Version,
···
132
148
}
133
149
}
134
150
135
135
-
var run = &cli.Command{
151
151
+
var runServe = &cli.Command{
136
152
Name: "run",
137
153
Usage: "Start the cocoon PDS",
138
154
Flags: []cli.Flag{},
···
177
193
return nil
178
194
},
179
195
}
196
196
+
197
197
+
var runCreateRotationKey = &cli.Command{
198
198
+
Name: "create-rotation-key",
199
199
+
Usage: "creates a rotation key for your pds",
200
200
+
Flags: []cli.Flag{
201
201
+
&cli.StringFlag{
202
202
+
Name: "out",
203
203
+
Required: true,
204
204
+
Usage: "output file for your rotation key",
205
205
+
},
206
206
+
},
207
207
+
Action: func(cmd *cli.Context) error {
208
208
+
key, err := crypto.GeneratePrivateKeyK256()
209
209
+
if err != nil {
210
210
+
return err
211
211
+
}
212
212
+
213
213
+
bytes := key.Bytes()
214
214
+
215
215
+
if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil {
216
216
+
return err
217
217
+
}
218
218
+
219
219
+
return nil
220
220
+
},
221
221
+
}
222
222
+
223
223
+
var runCreatePrivateJwk = &cli.Command{
224
224
+
Name: "create-private-jwk",
225
225
+
Usage: "creates a private jwk for your pds",
226
226
+
Flags: []cli.Flag{
227
227
+
&cli.StringFlag{
228
228
+
Name: "out",
229
229
+
Required: true,
230
230
+
Usage: "output file for your jwk",
231
231
+
},
232
232
+
},
233
233
+
Action: func(cmd *cli.Context) error {
234
234
+
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
235
235
+
if err != nil {
236
236
+
return err
237
237
+
}
238
238
+
239
239
+
key, err := jwk.FromRaw(privKey)
240
240
+
if err != nil {
241
241
+
return err
242
242
+
}
243
243
+
244
244
+
kid := fmt.Sprintf("%d", time.Now().Unix())
245
245
+
246
246
+
if err := key.Set(jwk.KeyIDKey, kid); err != nil {
247
247
+
return err
248
248
+
}
249
249
+
250
250
+
b, err := json.Marshal(key)
251
251
+
if err != nil {
252
252
+
return err
253
253
+
}
254
254
+
255
255
+
if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil {
256
256
+
return err
257
257
+
}
258
258
+
259
259
+
return nil
260
260
+
},
261
261
+
}
262
262
+
263
263
+
var runCreateInviteCode = &cli.Command{
264
264
+
Name: "create-invite-code",
265
265
+
Usage: "creates an invite code",
266
266
+
Flags: []cli.Flag{
267
267
+
&cli.StringFlag{
268
268
+
Name: "for",
269
269
+
Usage: "optional did to assign the invite code to",
270
270
+
},
271
271
+
&cli.IntFlag{
272
272
+
Name: "uses",
273
273
+
Usage: "number of times the invite code can be used",
274
274
+
Value: 1,
275
275
+
},
276
276
+
},
277
277
+
Action: func(cmd *cli.Context) error {
278
278
+
db, err := newDb()
279
279
+
if err != nil {
280
280
+
return err
281
281
+
}
282
282
+
283
283
+
forDid := "did:plc:123"
284
284
+
if cmd.String("for") != "" {
285
285
+
did, err := syntax.ParseDID(cmd.String("for"))
286
286
+
if err != nil {
287
287
+
return err
288
288
+
}
289
289
+
290
290
+
forDid = did.String()
291
291
+
}
292
292
+
293
293
+
uses := cmd.Int("uses")
294
294
+
295
295
+
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8))
296
296
+
297
297
+
if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil {
298
298
+
return err
299
299
+
}
300
300
+
301
301
+
fmt.Printf("New invite code created with %d uses: %s\n", uses, code)
302
302
+
303
303
+
return nil
304
304
+
},
305
305
+
}
306
306
+
307
307
+
var runResetPassword = &cli.Command{
308
308
+
Name: "reset-password",
309
309
+
Usage: "resets a password",
310
310
+
Flags: []cli.Flag{
311
311
+
&cli.StringFlag{
312
312
+
Name: "did",
313
313
+
Usage: "did of the user who's password you want to reset",
314
314
+
},
315
315
+
},
316
316
+
Action: func(cmd *cli.Context) error {
317
317
+
db, err := newDb()
318
318
+
if err != nil {
319
319
+
return err
320
320
+
}
321
321
+
322
322
+
didStr := cmd.String("did")
323
323
+
did, err := syntax.ParseDID(didStr)
324
324
+
if err != nil {
325
325
+
return err
326
326
+
}
327
327
+
328
328
+
newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12))
329
329
+
hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10)
330
330
+
if err != nil {
331
331
+
return err
332
332
+
}
333
333
+
334
334
+
if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil {
335
335
+
return err
336
336
+
}
337
337
+
338
338
+
fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass)
339
339
+
340
340
+
return nil
341
341
+
},
342
342
+
}
343
343
+
344
344
+
func newDb() (*gorm.DB, error) {
345
345
+
return gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{})
346
346
+
}