alpha
Login
or
Join now
willdot.net
/
distributed-pds
forked from
willdot.net/cocoon
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
A fork of the Cocoon PDS but being made more distributed.
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
update email, reset password
author
Hailey
date
1 year ago
(Mar 29, 2025, 6:02 PM -0700)
commit
9a610af4
9a610af4b74f8a569c2f8b26f218bcdf9d7d12d4
parent
3aee1775
3aee177533f91a0de83f133959eb41fa58ec6727
+209
-17
10 changed files
Expand all
Collapse all
Unified
Split
README.md
models
models.go
server
handle_server_confirm_email.go
handle_server_request_email_confirmation.go
handle_server_request_email_update.go
handle_server_request_password_reset.go
handle_server_reset_password.go
handle_server_update_email.go
mail.go
server.go
+2
-2
README.md
Reviewed
···
32
32
33
33
- [ ] com.atproto.server.activateAccount
34
34
- [ ] com.atproto.server.checkAccountStatus
35
35
-
- [ ] com.atproto.server.confirmEmail
35
35
+
- [x] com.atproto.server.confirmEmail
36
36
- [x] com.atproto.server.createAccount
37
37
- [ ] com.atproto.server.deactivateAccount
38
38
- [ ] com.atproto.server.deleteAccount
···
43
43
- [ ] com.atproto.server.listAppPasswords
44
44
- [x] com.atproto.server.refreshSession
45
45
- [ ] com.atproto.server.requestAccountDelete
46
46
-
- [ ] com.atproto.server.requestEmailConfirmation
46
46
+
- [x] com.atproto.server.requestEmailConfirmation
47
47
- [ ] com.atproto.server.requestEmailUpdate
48
48
- [ ] com.atproto.server.requestPasswordReset
49
49
- [ ] com.atproto.server.reserveSigningKey
+15
-10
models/models.go
Reviewed
···
8
8
)
9
9
10
10
type Repo struct {
11
11
-
Did string `gorm:"primaryKey"`
12
12
-
CreatedAt time.Time
13
13
-
Email string `gorm:"uniqueIndex"`
14
14
-
EmailConfirmedAt *time.Time
15
15
-
EmailVerificationCode *string
16
16
-
Password string
17
17
-
SigningKey []byte
18
18
-
Rev string
19
19
-
Root []byte
20
20
-
Preferences []byte
11
11
+
Did string `gorm:"primaryKey"`
12
12
+
CreatedAt time.Time
13
13
+
Email string `gorm:"uniqueIndex"`
14
14
+
EmailConfirmedAt *time.Time
15
15
+
EmailVerificationCode *string
16
16
+
EmailVerificationCodeExpiresAt *time.Time
17
17
+
EmailUpdateCode *string
18
18
+
EmailUpdateCodeExpiresAt *time.Time
19
19
+
PasswordResetCode *string
20
20
+
PasswordResetCodeExpiresAt *time.Time
21
21
+
Password string
22
22
+
SigningKey []byte
23
23
+
Rev string
24
24
+
Root []byte
25
25
+
Preferences []byte
21
26
}
22
27
23
28
func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) {
+6
-2
server/handle_server_confirm_email.go
Reviewed
···
27
27
return helpers.InputError(e, nil)
28
28
}
29
29
30
30
-
if urepo.EmailVerificationCode == nil {
30
30
+
if urepo.EmailVerificationCode == nil || urepo.EmailVerificationCodeExpiresAt == nil {
31
31
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
32
32
}
33
33
···
35
35
return helpers.InputError(e, to.StringPtr("InvalidToken"))
36
36
}
37
37
38
38
+
if time.Now().UTC().After(*urepo.EmailVerificationCodeExpiresAt) {
39
39
+
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
40
40
+
}
41
41
+
38
42
now := time.Now().UTC()
39
43
40
40
-
if err := s.db.Exec("UPDATE repos SET email_verification_code = NULL, email_confirmed_at = ? WHERE did = ?", now, urepo.Repo.Did).Error; err != nil {
44
44
+
if err := s.db.Exec("UPDATE repos SET email_verification_code = NULL, email_verification_code_expires_at = NULL, email_confirmed_at = ? WHERE did = ?", now, urepo.Repo.Did).Error; err != nil {
41
45
s.logger.Error("error updating user", "error", err)
42
46
return helpers.ServerError(e, nil)
43
47
}
+3
-1
server/handle_server_request_email_confirmation.go
Reviewed
···
2
2
3
3
import (
4
4
"fmt"
5
5
+
"time"
5
6
6
7
"github.com/Azure/go-autorest/autorest/to"
7
8
"github.com/haileyok/cocoon/internal/helpers"
···
17
18
}
18
19
19
20
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(6), helpers.RandomVarchar(6))
21
21
+
eat := time.Now().Add(10 * time.Minute).UTC()
20
22
21
21
-
if err := s.db.Exec("UPDATE repos SET email_verification_code = ? WHERE did = ?", code, urepo.Repo.Did).Error; err != nil {
23
23
+
if err := s.db.Exec("UPDATE repos SET email_verification_code = ?, email_verification_code_expires_at = ? WHERE did = ?", code, eat, urepo.Repo.Did).Error; err != nil {
22
24
s.logger.Error("error updating user", "error", err)
23
25
return helpers.ServerError(e, nil)
24
26
}
+29
server/handle_server_request_email_update.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"fmt"
5
5
+
"time"
6
6
+
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/labstack/echo/v4"
10
10
+
)
11
11
+
12
12
+
func (s *Server) handleServerRequestEmailUpdate(e echo.Context) error {
13
13
+
urepo := e.Get("repo").(*models.RepoActor)
14
14
+
15
15
+
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(6), helpers.RandomVarchar(6))
16
16
+
eat := time.Now().Add(10 * time.Minute).UTC()
17
17
+
18
18
+
if err := s.db.Exec("UPDATE repos SET email_update_code = ?, email_update_code_expires_at = ? WHERE did = ?", code, eat, urepo.Repo.Did).Error; err != nil {
19
19
+
s.logger.Error("error updating repo", "error", err)
20
20
+
return helpers.ServerError(e, nil)
21
21
+
}
22
22
+
23
23
+
if err := s.sendEmailUpdate(urepo.Email, urepo.Handle, code); err != nil {
24
24
+
s.logger.Error("error sending email", "error", err)
25
25
+
return helpers.ServerError(e, nil)
26
26
+
}
27
27
+
28
28
+
return e.NoContent(200)
29
29
+
}
+29
server/handle_server_request_password_reset.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"fmt"
5
5
+
"time"
6
6
+
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/labstack/echo/v4"
10
10
+
)
11
11
+
12
12
+
func (s *Server) handleServerRequestPasswordReset(e echo.Context) error {
13
13
+
urepo := e.Get("repo").(*models.RepoActor)
14
14
+
15
15
+
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(6), helpers.RandomVarchar(6))
16
16
+
eat := time.Now().Add(10 * time.Minute).UTC()
17
17
+
18
18
+
if err := s.db.Exec("UPDATE repos SET password_reset_code = ?, password_reset_code_expires_at = ? WHERE did = ?", code, eat, urepo.Repo.Did).Error; err != nil {
19
19
+
s.logger.Error("error updating repo", "error", err)
20
20
+
return helpers.ServerError(e, nil)
21
21
+
}
22
22
+
23
23
+
if err := s.sendPasswordReset(urepo.Email, urepo.Handle, code); err != nil {
24
24
+
s.logger.Error("error sending email", "error", err)
25
25
+
return helpers.ServerError(e, nil)
26
26
+
}
27
27
+
28
28
+
return e.NoContent(200)
29
29
+
}
+55
server/handle_server_reset_password.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"time"
5
5
+
6
6
+
"github.com/Azure/go-autorest/autorest/to"
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/labstack/echo/v4"
10
10
+
"golang.org/x/crypto/bcrypt"
11
11
+
)
12
12
+
13
13
+
type ComAtprotoServerResetPasswordRequest struct {
14
14
+
Token string `json:"token" validate:"required"`
15
15
+
Password string `json:"password" validate:"required"`
16
16
+
}
17
17
+
18
18
+
func (s *Server) handleServerResetPassword(e echo.Context) error {
19
19
+
urepo := e.Get("repo").(*models.RepoActor)
20
20
+
21
21
+
var req ComAtprotoServerResetPasswordRequest
22
22
+
if err := e.Bind(&req); err != nil {
23
23
+
s.logger.Error("error binding", "error", err)
24
24
+
return helpers.ServerError(e, nil)
25
25
+
}
26
26
+
27
27
+
if err := e.Validate(req); err != nil {
28
28
+
return helpers.InputError(e, nil)
29
29
+
}
30
30
+
31
31
+
if urepo.PasswordResetCode == nil || urepo.PasswordResetCodeExpiresAt == nil {
32
32
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
33
33
+
}
34
34
+
35
35
+
if *urepo.PasswordResetCode != req.Token {
36
36
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
37
37
+
}
38
38
+
39
39
+
if time.Now().UTC().After(*urepo.PasswordResetCodeExpiresAt) {
40
40
+
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
41
41
+
}
42
42
+
43
43
+
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
44
44
+
if err != nil {
45
45
+
s.logger.Error("error creating hash", "error", err)
46
46
+
return helpers.ServerError(e, nil)
47
47
+
}
48
48
+
49
49
+
if err := s.db.Exec("UPDATE repos SET password_reset_code = NULL, password_reset_code_expires_at = NULL, password = ? WHERE did = ?", hash, urepo.Repo.Did).Error; err != nil {
50
50
+
s.logger.Error("error updating repo", "error", err)
51
51
+
return helpers.ServerError(e, nil)
52
52
+
}
53
53
+
54
54
+
return e.NoContent(200)
55
55
+
}
+49
server/handle_server_update_email.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"time"
5
5
+
6
6
+
"github.com/Azure/go-autorest/autorest/to"
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/labstack/echo/v4"
10
10
+
)
11
11
+
12
12
+
type ComAtprotoServerUpdateEmailRequest struct {
13
13
+
Email string `json:"email" validate:"required"`
14
14
+
EmailAuthFactor bool `json:"emailAuthFactor"`
15
15
+
Token string `json:"token" validate:"required"`
16
16
+
}
17
17
+
18
18
+
func (s *Server) handleServerUpdateEmail(e echo.Context) error {
19
19
+
urepo := e.Get("repo").(*models.RepoActor)
20
20
+
21
21
+
var req ComAtprotoServerUpdateEmailRequest
22
22
+
if err := e.Bind(&req); err != nil {
23
23
+
s.logger.Error("error binding", "error", err)
24
24
+
return helpers.ServerError(e, nil)
25
25
+
}
26
26
+
27
27
+
if err := e.Validate(req); err != nil {
28
28
+
return helpers.InputError(e, nil)
29
29
+
}
30
30
+
31
31
+
if urepo.EmailUpdateCode == nil || urepo.EmailUpdateCodeExpiresAt == nil {
32
32
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
33
33
+
}
34
34
+
35
35
+
if *urepo.EmailUpdateCode != req.Token {
36
36
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
37
37
+
}
38
38
+
39
39
+
if time.Now().UTC().After(*urepo.EmailUpdateCodeExpiresAt) {
40
40
+
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
41
41
+
}
42
42
+
43
43
+
if err := s.db.Exec("UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email = ? WHERE did = ?", req.Email, urepo.Repo.Did).Error; err != nil {
44
44
+
s.logger.Error("error updating repo", "error", err)
45
45
+
return helpers.ServerError(e, nil)
46
46
+
}
47
47
+
48
48
+
return e.NoContent(200)
49
49
+
}
+17
-2
server/mail.go
Reviewed
···
23
23
24
24
s.mail.To(email)
25
25
s.mail.Subject("Password reset for " + s.config.Hostname)
26
26
-
s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your password reset code is %s.", handle, code))
26
26
+
s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your password reset code is %s. This code will expire in ten minutes.", handle, code))
27
27
+
28
28
+
if err := s.mail.Send(); err != nil {
29
29
+
return err
30
30
+
}
31
31
+
32
32
+
return nil
33
33
+
}
34
34
+
35
35
+
func (s *Server) sendEmailUpdate(email, handle, code string) error {
36
36
+
s.mailLk.Lock()
37
37
+
defer s.mailLk.Unlock()
38
38
+
39
39
+
s.mail.To(email)
40
40
+
s.mail.Subject("Email update for " + s.config.Hostname)
41
41
+
s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your email update code is %s. This code will expire in ten minutes.", handle, code))
27
42
28
43
if err := s.mail.Send(); err != nil {
29
44
return err
···
38
53
39
54
s.mail.To(email)
40
55
s.mail.Subject("Email verification for " + s.config.Hostname)
41
41
-
s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your email verification code is %s", handle, code))
56
56
+
s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your email verification code is %s. This code will expire in ten minutes.", handle, code))
42
57
43
58
if err := s.mail.Send(); err != nil {
44
59
return err
+4
server/server.go
Reviewed
···
383
383
s.echo.POST("/xrpc/com.atproto.identity.updateHandle", s.handleIdentityUpdateHandle, s.handleSessionMiddleware)
384
384
s.echo.POST("/xrpc/com.atproto.server.confirmEmail", s.handleServerConfirmEmail, s.handleSessionMiddleware)
385
385
s.echo.POST("/xrpc/com.atproto.server.requestEmailConfirmation", s.handleServerRequestEmailConfirmation, s.handleSessionMiddleware)
386
386
+
s.echo.POST("/xrpc/com.atproto.server.requestPasswordReset", s.handleServerRequestPasswordReset, s.handleSessionMiddleware)
387
387
+
s.echo.POST("/xrpc/com.atproto.server.requestEmailUpdate", s.handleServerRequestEmailUpdate, s.handleSessionMiddleware)
388
388
+
s.echo.POST("/xrpc/com.atproto.server.resetPassword", s.handleServerResetPassword, s.handleSessionMiddleware)
389
389
+
s.echo.POST("/xrpc/com.atproto.server.updateEmail", s.handleServerUpdateEmail, s.handleSessionMiddleware)
386
390
387
391
// repo
388
392
s.echo.POST("/xrpc/com.atproto.repo.createRecord", s.handleCreateRecord, s.handleSessionMiddleware)