mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
d43ce07b66
Independently of this, ClientLookupSet.Resume ensures that at most one resume can succeed, so this doesn't actually change the behavior. But ResumeManager should be a standalone example of how to implement resume without race conditions.
91 lines
2.1 KiB
Go
91 lines
2.1 KiB
Go
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
|
// released under the MIT license
|
|
|
|
package irc
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/oragono/oragono/irc/utils"
|
|
)
|
|
|
|
// implements draft/resume-0.3, in particular the issuing, management, and verification
|
|
// of resume tokens with two components: a unique ID and a secret key
|
|
|
|
type resumeTokenPair struct {
|
|
client *Client
|
|
secret string
|
|
}
|
|
|
|
type ResumeManager struct {
|
|
sync.RWMutex // level 2
|
|
|
|
resumeIDtoCreds map[string]resumeTokenPair
|
|
server *Server
|
|
}
|
|
|
|
func (rm *ResumeManager) Initialize(server *Server) {
|
|
rm.resumeIDtoCreds = make(map[string]resumeTokenPair)
|
|
rm.server = server
|
|
}
|
|
|
|
// GenerateToken generates a resume token for a client. If the client has
|
|
// already been assigned one, it returns "".
|
|
func (rm *ResumeManager) GenerateToken(client *Client) (token string) {
|
|
id := utils.GenerateSecretToken()
|
|
secret := utils.GenerateSecretToken()
|
|
|
|
rm.Lock()
|
|
defer rm.Unlock()
|
|
|
|
if client.ResumeID() != "" {
|
|
return
|
|
}
|
|
|
|
client.SetResumeID(id)
|
|
rm.resumeIDtoCreds[id] = resumeTokenPair{
|
|
client: client,
|
|
secret: secret,
|
|
}
|
|
|
|
return id + secret
|
|
}
|
|
|
|
// VerifyToken looks up the client corresponding to a resume token, returning
|
|
// nil if there is no such client or the token is invalid. If successful,
|
|
// the token is consumed and cannot be used to resume again.
|
|
func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
|
|
if len(token) != 2*utils.SecretTokenLength {
|
|
return
|
|
}
|
|
|
|
rm.RLock()
|
|
defer rm.RUnlock()
|
|
|
|
id := token[:utils.SecretTokenLength]
|
|
pair, ok := rm.resumeIDtoCreds[id]
|
|
if ok {
|
|
if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
|
|
// disallow resume of an unregistered client; this prevents the use of
|
|
// resume as an auth bypass
|
|
if pair.client.Registered() {
|
|
// consume the token, ensuring that at most one resume can succeed
|
|
delete(rm.resumeIDtoCreds, id)
|
|
return pair.client
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Delete stops tracking a client's resume token.
|
|
func (rm *ResumeManager) Delete(client *Client) {
|
|
rm.Lock()
|
|
defer rm.Unlock()
|
|
|
|
currentID := client.ResumeID()
|
|
if currentID != "" {
|
|
delete(rm.resumeIDtoCreds, currentID)
|
|
}
|
|
}
|