package invite import ( "crypto/rand" "database/sql" "errors" "math/big" "whitelistmanager/internal/store" ) const NotOwnerofServer = "user is not owner of server" type Manager struct { store store.Storer } type InviteManager interface { Create(in store.Invite, user store.User) (string, error) RemainingUses(in store.Invite) (int, error) } func NewManager(db store.Storer) *Manager { return &Manager{store: db} } func (i *Manager) Create(in store.Invite, user store.User) (string, error) { server, err := i.store.GetServer(in.Server.Id) if err != nil { return "", err } if server.Owner.Id != user.Id { return "", errors.New(NotOwnerofServer) } token, err := i.createToken() if err != nil { return "", err } // If we have an unlikely collision, generate a new token. _, err = i.store.GetInvite(token) if err != nil { if !errors.Is(err, sql.ErrNoRows) { return "", err } } else { token, err = i.createToken() if err != nil { return "", err } } in.Token = token in.Creator = user return token, i.store.SaveInvite(in) } func (i *Manager) createToken() (string, error) { const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ret := make([]byte, 8) for i := 0; i < 8; i++ { num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) if err != nil { return "", err } ret[i] = letters[num.Int64()] } return string(ret), nil } func (i *Manager) RemainingUses(in store.Invite) (int, error) { if in.Unlimited { return 1, nil } logs, err := i.store.InviteLog(in) if err != nil { return 0, nil } return in.Uses - len(logs), nil }