First pass at adding a waiting room.
This commit is contained in:
@@ -133,6 +133,12 @@ type ClientCredentials struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// AuthenticatedClient is the credential-free result of authentication.
|
||||
type AuthenticatedClient struct {
|
||||
Username string
|
||||
Permissions []string
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
Hall() *Hall
|
||||
Addr() net.Addr
|
||||
|
||||
+142
-83
@@ -105,6 +105,7 @@ type Hall struct {
|
||||
description *Description
|
||||
locked *string
|
||||
clients map[string]Client
|
||||
reserved map[string]Client
|
||||
history []ChatHistoryEntry
|
||||
timestamp time.Time
|
||||
data map[string]interface{}
|
||||
@@ -232,6 +233,37 @@ func (g *Hall) ClientCount() int {
|
||||
return g.clientCountUnlocked()
|
||||
}
|
||||
|
||||
// ReserveUsernameWhileWaiting atomically checks whether waiting is available
|
||||
// and, if so, reserves a username for an authenticated non-member.
|
||||
func (g *Hall) ReserveUsernameWhileWaiting(username string, client Client) (bool, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.locked == nil || !g.hasOperatorUnlocked() {
|
||||
return false, nil
|
||||
}
|
||||
for _, c := range g.clients {
|
||||
if !isSystemClient(c) && c.Username() == username {
|
||||
return true, UserError("username already in use")
|
||||
}
|
||||
}
|
||||
if holder := g.reserved[username]; holder != nil && holder != client {
|
||||
return true, UserError("username already in use")
|
||||
}
|
||||
if g.reserved == nil {
|
||||
g.reserved = make(map[string]Client)
|
||||
}
|
||||
g.reserved[username] = client
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (g *Hall) ReleaseUsername(username string, client Client) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.reserved[username] == client {
|
||||
delete(g.reserved, username)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Hall) clientCountUnlocked() int {
|
||||
count := 0
|
||||
for _, c := range g.clients {
|
||||
@@ -252,6 +284,9 @@ func (g *Hall) mayExpire() bool {
|
||||
if len(g.clients) > 0 {
|
||||
return false
|
||||
}
|
||||
if len(g.reserved) > 0 {
|
||||
return false
|
||||
}
|
||||
return time.Since(g.timestamp) > maxHistoryAge(g.description)
|
||||
}
|
||||
|
||||
@@ -578,7 +613,7 @@ func Delete(name string) bool {
|
||||
|
||||
// Called with both halls.mu and g.mu taken.
|
||||
func deleteUnlocked(g *Hall) bool {
|
||||
if len(g.clients) != 0 {
|
||||
if len(g.clients) != 0 || len(g.reserved) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -599,112 +634,136 @@ func isSystemClient(c Client) bool {
|
||||
return member("system", c.Permissions())
|
||||
}
|
||||
|
||||
func AddClient(hall string, c Client, creds ClientCredentials) (*Hall, error) {
|
||||
g, err := Add(hall, nil)
|
||||
func AuthenticateClient(name string, creds ClientCredentials) (*Hall, AuthenticatedClient, error) {
|
||||
g, err := Add(name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, AuthenticatedClient{}, err
|
||||
}
|
||||
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
username, perms, err := g.getPermission(creds)
|
||||
if err != nil {
|
||||
return nil, AuthenticatedClient{}, err
|
||||
}
|
||||
return g, AuthenticatedClient{username, append([]string(nil), perms...)}, nil
|
||||
}
|
||||
|
||||
// AddAuthenticatedClient admits a previously authenticated non-system client.
|
||||
// A lock bypass does not bypass time restrictions or capacity.
|
||||
func AddAuthenticatedClient(g *Hall, c Client, auth AuthenticatedClient, bypassLock bool) error {
|
||||
if g == nil {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
clients := g.getClientsUnlocked(nil)
|
||||
systemClient := isSystemClient(c)
|
||||
|
||||
if !systemClient {
|
||||
username, perms, err := g.getPermission(creds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if holder := g.reserved[auth.Username]; holder != nil && holder != c {
|
||||
return UserError("username already in use")
|
||||
}
|
||||
c.SetUsername(auth.Username)
|
||||
c.SetPermissions(auth.Permissions)
|
||||
for _, cc := range clients {
|
||||
if !isSystemClient(cc) && cc.Username() == auth.Username {
|
||||
return UserError("username already in use")
|
||||
}
|
||||
|
||||
c.SetUsername(username)
|
||||
c.SetPermissions(perms)
|
||||
|
||||
for _, cc := range clients {
|
||||
if !isSystemClient(cc) && cc.Username() == username {
|
||||
return nil, UserError("username already in use")
|
||||
}
|
||||
if !member("op", auth.Permissions) {
|
||||
if !bypassLock && g.locked != nil {
|
||||
m := *g.locked
|
||||
if m == "" {
|
||||
m = "Hall is locked."
|
||||
}
|
||||
return UserError(m)
|
||||
}
|
||||
now := time.Now()
|
||||
if g.description.NotBefore != nil && g.description.NotBefore.After(now) {
|
||||
return UserError("this hall is not open yet")
|
||||
}
|
||||
if g.description.Expires != nil && g.description.Expires.Before(now) {
|
||||
return UserError("this hall is closed")
|
||||
}
|
||||
if g.description.Autokick {
|
||||
ops := false
|
||||
for _, existing := range clients {
|
||||
if member("op", existing.Permissions()) {
|
||||
ops = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ops {
|
||||
return UserError("there are no operators in this hall")
|
||||
}
|
||||
}
|
||||
|
||||
if !member("op", perms) {
|
||||
if g.locked != nil {
|
||||
m := *g.locked
|
||||
if m == "" {
|
||||
m = "Hall is locked."
|
||||
}
|
||||
return nil, UserError(m)
|
||||
}
|
||||
if g.description.NotBefore != nil ||
|
||||
g.description.Expires != nil {
|
||||
now := time.Now()
|
||||
if g.description.NotBefore != nil &&
|
||||
g.description.NotBefore.After(now) {
|
||||
return nil, UserError(
|
||||
"this hall is not open yet",
|
||||
)
|
||||
}
|
||||
if g.description.Expires != nil &&
|
||||
g.description.Expires.Before(now) {
|
||||
return nil, UserError(
|
||||
"this hall is closed",
|
||||
)
|
||||
}
|
||||
}
|
||||
if g.description.Autokick {
|
||||
ops := false
|
||||
for _, c := range clients {
|
||||
if member("op", c.Permissions()) {
|
||||
ops = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ops {
|
||||
return nil, UserError(
|
||||
"there are no operators " +
|
||||
"in this hall",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !member("op", perms) && g.description.MaxClients > 0 {
|
||||
if g.clientCountUnlocked() >= g.description.MaxClients {
|
||||
return nil, UserError("too many users")
|
||||
}
|
||||
if g.description.MaxClients > 0 && g.clientCountUnlocked() >= g.description.MaxClients {
|
||||
return UserError("too many users")
|
||||
}
|
||||
}
|
||||
id := c.Id()
|
||||
if id == "" {
|
||||
return nil, errors.New("client has empty id")
|
||||
return errors.New("client has empty id")
|
||||
}
|
||||
if g.clients[id] != nil {
|
||||
return nil, ProtocolError("duplicate client id")
|
||||
return ProtocolError("duplicate client id")
|
||||
}
|
||||
g.clients[id] = c
|
||||
delete(g.reserved, auth.Username)
|
||||
g.timestamp = time.Now()
|
||||
|
||||
c.Joined(g.Name(), "join")
|
||||
|
||||
u := c.Username()
|
||||
p := c.Permissions()
|
||||
s := c.Data()
|
||||
if !systemClient {
|
||||
c.PushClient(g.Name(), "add", c.Id(), u, p, s)
|
||||
}
|
||||
u, p, s := c.Username(), c.Permissions(), c.Data()
|
||||
c.PushClient(g.Name(), "add", id, u, p, s)
|
||||
for _, cc := range clients {
|
||||
pp := cc.Permissions()
|
||||
uu := cc.Username()
|
||||
if !isSystemClient(cc) {
|
||||
c.PushClient(
|
||||
g.Name(), "add", cc.Id(), uu, pp, cc.Data(),
|
||||
)
|
||||
c.PushClient(g.Name(), "add", cc.Id(), cc.Username(), cc.Permissions(), cc.Data())
|
||||
}
|
||||
if !systemClient {
|
||||
cc.PushClient(g.Name(), "add", id, u, p, s)
|
||||
cc.PushClient(g.Name(), "add", id, u, p, s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddClient(name string, c Client, creds ClientCredentials) (*Hall, error) {
|
||||
if creds.System || isSystemClient(c) {
|
||||
g, err := Add(name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if c.Id() == "" {
|
||||
return nil, errors.New("client has empty id")
|
||||
}
|
||||
if g.clients[c.Id()] != nil {
|
||||
return nil, ProtocolError("duplicate client id")
|
||||
}
|
||||
g.clients[c.Id()] = c
|
||||
g.timestamp = time.Now()
|
||||
c.Joined(g.Name(), "join")
|
||||
return g, nil
|
||||
}
|
||||
g, auth, err := AuthenticateClient(name, creds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := AddAuthenticatedClient(g, c, auth, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// WaitingStatus atomically reports the lock and active-operator state.
|
||||
func (g *Hall) WaitingStatus() (bool, bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.locked != nil, g.hasOperatorUnlocked()
|
||||
}
|
||||
|
||||
// called locked
|
||||
func (g *Hall) hasOperatorUnlocked() bool {
|
||||
for _, c := range g.clients {
|
||||
if !isSystemClient(c) && member("op", c.Permissions()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return g, nil
|
||||
return false
|
||||
}
|
||||
|
||||
// called locked
|
||||
|
||||
Reference in New Issue
Block a user