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