2019-02-12 06:27:57 +01:00
|
|
|
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package irc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/oragono/oragono/irc/utils"
|
|
|
|
)
|
|
|
|
|
2019-05-22 03:40:25 +02:00
|
|
|
// implements draft/resume, in particular the issuing, management, and verification
|
2019-02-12 06:27:57 +01:00
|
|
|
// of resume tokens with two components: a unique ID and a secret key
|
|
|
|
|
|
|
|
type resumeTokenPair struct {
|
|
|
|
client *Client
|
|
|
|
secret string
|
|
|
|
}
|
|
|
|
|
|
|
|
type ResumeManager struct {
|
2019-05-19 11:47:41 +02:00
|
|
|
sync.Mutex // level 2
|
2019-02-12 06:27:57 +01:00
|
|
|
|
|
|
|
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 "".
|
2019-05-22 03:40:25 +02:00
|
|
|
func (rm *ResumeManager) GenerateToken(client *Client) (token string, id string) {
|
|
|
|
id = utils.GenerateSecretToken()
|
2019-02-12 06:27:57 +01:00
|
|
|
secret := utils.GenerateSecretToken()
|
|
|
|
|
|
|
|
rm.Lock()
|
|
|
|
defer rm.Unlock()
|
|
|
|
|
|
|
|
if client.ResumeID() != "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
client.SetResumeID(id)
|
|
|
|
rm.resumeIDtoCreds[id] = resumeTokenPair{
|
|
|
|
client: client,
|
|
|
|
secret: secret,
|
|
|
|
}
|
|
|
|
|
2019-05-22 03:40:25 +02:00
|
|
|
return id + secret, id
|
2019-02-12 06:27:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// VerifyToken looks up the client corresponding to a resume token, returning
|
2019-02-19 22:47:04 +01:00
|
|
|
// 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.
|
2019-05-22 03:40:25 +02:00
|
|
|
func (rm *ResumeManager) VerifyToken(newClient *Client, token string) (oldClient *Client, id string) {
|
2019-02-12 06:27:57 +01:00
|
|
|
if len(token) != 2*utils.SecretTokenLength {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-05-19 11:47:41 +02:00
|
|
|
rm.Lock()
|
|
|
|
defer rm.Unlock()
|
2019-02-12 06:27:57 +01:00
|
|
|
|
2019-05-22 03:40:25 +02:00
|
|
|
id = token[:utils.SecretTokenLength]
|
2019-02-12 06:27:57 +01:00
|
|
|
pair, ok := rm.resumeIDtoCreds[id]
|
2019-05-22 03:40:25 +02:00
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// disallow resume of an unregistered client; this prevents the use of
|
|
|
|
// resume as an auth bypass
|
|
|
|
if !pair.client.Registered() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
|
|
|
|
oldClient = pair.client // success!
|
|
|
|
// consume the token, ensuring that at most one resume can succeed
|
|
|
|
delete(rm.resumeIDtoCreds, id)
|
|
|
|
// old client is henceforth resumeable under new client's creds (possibly empty)
|
|
|
|
newResumeID := newClient.ResumeID()
|
|
|
|
oldClient.SetResumeID(newResumeID)
|
|
|
|
if newResumeID != "" {
|
|
|
|
if newResumeCreds, ok := rm.resumeIDtoCreds[newResumeID]; ok {
|
|
|
|
newResumeCreds.client = oldClient
|
|
|
|
rm.resumeIDtoCreds[newResumeID] = newResumeCreds
|
2019-02-12 06:27:57 +01:00
|
|
|
}
|
|
|
|
}
|
2019-05-22 03:40:25 +02:00
|
|
|
// new client no longer "owns" newResumeID, remove the association
|
|
|
|
newClient.SetResumeID("")
|
2019-02-12 06:27:57 +01:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|