package db import ( "errors" "farga/db/types" "farga/lib" "time" "github.com/google/uuid" "gorm.io/gorm" ) var ErrInvalidInvite = errors.New("invalid or already-used invite token") type NewInviteOptions struct { UserID uuid.UUID // optional Length uint Duration time.Duration Role uint // user role to assign. By default, set it to normal user. } // creates a temporary new invite token. func (db *DB) NewInvite(o NewInviteOptions) (types.Invite, error) { if o.Length == 0 { return types.Invite{}, ErrInvalidInvite } if o.Duration == 0 { return types.Invite{}, ErrInvalidInvite } // nil for anonymous (e.g. cli-issued admin) invites, so the FK stays unset. var userID *uuid.UUID if o.UserID != uuid.Nil { userID = &o.UserID } var invite = types.Invite{ UserID: userID, Token: lib.AlphanumericToken(o.Length), ExpiresAt: time.Now().Add(o.Duration), Role: o.Role, } err := db.Create(&invite).Error return invite, err } // validates a given invite token. Returns `true` if it exists and isn't expired. Otherwise false. // it will ONLY delete the invite token if it expired. func (db *DB) ValidateInviteToken(token string) (types.Invite, bool) { if token == "" { return types.Invite{}, false } var invite types.Invite err := db.Where("token = ?", token).Find(&invite).Error // if it doesn't exist if err != nil || invite.ID == uuid.Nil { return types.Invite{}, false } // if it expired if time.Now().After(invite.ExpiresAt) { db.Delete(&invite) return types.Invite{}, false } return invite, true } // deletes a given invite token. func (db *DB) DeleteInviteToken(token string) error { var invite = types.Invite{ Token: token, } err := db.Model(&invite).Where(&invite).Delete(&invite).Error return err } // checks invite tokens' expiresAt and if they expired, it removes them func (db *DB) DeleteExpiredInviteTokens() { db.Where("expires_at < ?", time.Now()).Delete(&types.Invite{}) } // creates a new user and checks if the invite. func (db *DB) NewUserWithInvite(user *types.User, inviteToken string) error { return db.Transaction(func(tx *gorm.DB) error { res := tx.Model(&types.Invite{}). Where("token = ? AND expires_at > ?", inviteToken, time.Now()). Delete(&types.Invite{}) if res.Error != nil { return res.Error } if res.RowsAffected != 1 { return ErrInvalidInvite } return tx.Create(user).Error }) }