feat: get signup via discord working
This commit is contained in:
parent
77dea0c5ed
commit
9a3c51459b
6 changed files with 183 additions and 18 deletions
|
@ -46,7 +46,7 @@ const (
|
|||
)
|
||||
|
||||
// CreateUser creates a user with the given username.
|
||||
func (db *DB) CreateUser(ctx context.Context, username string) (u User, err error) {
|
||||
func (db *DB) CreateUser(ctx context.Context, tx pgx.Tx, username string) (u User, err error) {
|
||||
// check if the username is valid
|
||||
// if not, return an error depending on what failed
|
||||
if !usernameRegex.MatchString(username) {
|
||||
|
@ -64,7 +64,7 @@ func (db *DB) CreateUser(ctx context.Context, username string) (u User, err erro
|
|||
return u, errors.Wrap(err, "building sql")
|
||||
}
|
||||
|
||||
err = pgxscan.Get(ctx, db, &u, sql, args...)
|
||||
err = pgxscan.Get(ctx, tx, &u, sql, args...)
|
||||
if err != nil {
|
||||
if v, ok := errors.Cause(err).(*pgconn.PgError); ok {
|
||||
if v.Code == "23505" { // unique constraint violation
|
||||
|
|
|
@ -7,8 +7,10 @@ import (
|
|||
"codeberg.org/u1f320/pronouns.cc/backend/db"
|
||||
"codeberg.org/u1f320/pronouns.cc/backend/log"
|
||||
"codeberg.org/u1f320/pronouns.cc/backend/server"
|
||||
"emperror.dev/errors"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/mediocregopher/radix/v4"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
@ -37,7 +39,7 @@ type discordCallbackResponse struct {
|
|||
|
||||
Discord string `json:"discord,omitempty"` // username, for UI purposes
|
||||
Ticket string `json:"ticket,omitempty"`
|
||||
RequireInvite bool `json:"require_invite,omitempty"` // require an invite for signing up
|
||||
RequireInvite bool `json:"require_invite"` // require an invite for signing up
|
||||
}
|
||||
|
||||
func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
|
@ -114,6 +116,108 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type signupRequest struct {
|
||||
Ticket string `json:"ticket"`
|
||||
Username string `json:"username"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
}
|
||||
|
||||
type signupResponse struct {
|
||||
User userResponse `json:"user"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (s *Server) discordSignup(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := Decode[signupRequest](r)
|
||||
if err != nil {
|
||||
return server.APIError{Code: server.ErrBadRequest}
|
||||
}
|
||||
|
||||
if s.RequireInvite && req.InviteCode == "" {
|
||||
return server.APIError{Code: server.ErrInviteRequired}
|
||||
}
|
||||
|
||||
valid, taken, err := s.DB.UsernameTaken(ctx, req.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid {
|
||||
return server.APIError{Code: server.ErrInvalidUsername}
|
||||
}
|
||||
if taken {
|
||||
return server.APIError{Code: server.ErrUsernameTaken}
|
||||
}
|
||||
|
||||
tx, err := s.DB.Begin(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "beginning transaction")
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
var du discordgo.User
|
||||
err = s.DB.GetJSON(ctx, "discord:"+req.Ticket, &du)
|
||||
if err != nil {
|
||||
log.Errorf("getting discord user for ticket: %v", err)
|
||||
|
||||
return server.APIError{Code: server.ErrInvalidTicket}
|
||||
}
|
||||
|
||||
u, err := s.DB.CreateUser(ctx, tx, req.Username)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating user")
|
||||
}
|
||||
|
||||
err = u.UpdateFromDiscord(ctx, tx, &du)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == db.ErrUsernameTaken {
|
||||
return server.APIError{Code: server.ErrUsernameTaken}
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "updating user from discord")
|
||||
}
|
||||
|
||||
if s.RequireInvite {
|
||||
// TODO: check invites, invalidate invite when done
|
||||
inviteValid := true
|
||||
|
||||
if !inviteValid {
|
||||
err = tx.Rollback(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "rolling back transaction")
|
||||
}
|
||||
|
||||
return server.APIError{Code: server.ErrInviteRequired}
|
||||
}
|
||||
}
|
||||
|
||||
// delete sign up ticket
|
||||
err = s.DB.Redis.Do(ctx, radix.Cmd(nil, "DEL", "discord:"+req.Ticket))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deleting signup ticket")
|
||||
}
|
||||
|
||||
// commit transaction
|
||||
err = tx.Commit(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "committing transaction")
|
||||
}
|
||||
|
||||
// create token
|
||||
token, err := s.Auth.CreateToken(u.ID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating token")
|
||||
}
|
||||
|
||||
// return user
|
||||
render.JSON(w, r, signupResponse{
|
||||
User: *dbUserToUserResponse(u),
|
||||
Token: token,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func Decode[T any](r *http.Request) (T, error) {
|
||||
decoded := *new(T)
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ func Mount(srv *server.Server, r chi.Router) {
|
|||
// takes code + state, validates it, returns token OR discord signup ticket
|
||||
r.Post("/callback", server.WrapHandler(s.discordCallback))
|
||||
// takes discord signup ticket to register account
|
||||
r.Post("/signup", nil)
|
||||
r.Post("/signup", server.WrapHandler(s.discordSignup))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -76,6 +76,10 @@ const (
|
|||
ErrInvalidState = 1001
|
||||
ErrInvalidOAuthCode = 1002
|
||||
ErrInvalidToken = 1003 // a token was supplied, but it is invalid
|
||||
ErrInviteRequired = 1004
|
||||
ErrInvalidTicket = 1005 // invalid signup ticket
|
||||
ErrInvalidUsername = 1006 // invalid username (when signing up)
|
||||
ErrUsernameTaken = 1007 // username taken (when signing up)
|
||||
|
||||
// User-related error codes
|
||||
ErrUserNotFound = 2001
|
||||
|
@ -99,6 +103,10 @@ var errCodeMessages = map[int]string{
|
|||
ErrInvalidState: "Invalid OAuth state",
|
||||
ErrInvalidOAuthCode: "Invalid OAuth code",
|
||||
ErrInvalidToken: "Supplied token was invalid",
|
||||
ErrInviteRequired: "A valid invite code is required",
|
||||
ErrInvalidTicket: "Invalid signup ticket",
|
||||
ErrInvalidUsername: "Invalid username",
|
||||
ErrUsernameTaken: "Username is already taken",
|
||||
|
||||
ErrUserNotFound: "User not found",
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue