You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

resume.go 2.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. // Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "sync"
  6. "github.com/oragono/oragono/irc/utils"
  7. )
  8. // implements draft/resume-0.3, in particular the issuing, management, and verification
  9. // of resume tokens with two components: a unique ID and a secret key
  10. type resumeTokenPair struct {
  11. client *Client
  12. secret string
  13. }
  14. type ResumeManager struct {
  15. sync.RWMutex // level 2
  16. resumeIDtoCreds map[string]resumeTokenPair
  17. server *Server
  18. }
  19. func (rm *ResumeManager) Initialize(server *Server) {
  20. rm.resumeIDtoCreds = make(map[string]resumeTokenPair)
  21. rm.server = server
  22. }
  23. // GenerateToken generates a resume token for a client. If the client has
  24. // already been assigned one, it returns "".
  25. func (rm *ResumeManager) GenerateToken(client *Client) (token string) {
  26. id := utils.GenerateSecretToken()
  27. secret := utils.GenerateSecretToken()
  28. rm.Lock()
  29. defer rm.Unlock()
  30. if client.ResumeID() != "" {
  31. return
  32. }
  33. client.SetResumeID(id)
  34. rm.resumeIDtoCreds[id] = resumeTokenPair{
  35. client: client,
  36. secret: secret,
  37. }
  38. return id + secret
  39. }
  40. // VerifyToken looks up the client corresponding to a resume token, returning
  41. // nil if there is no such client or the token is invalid. If successful,
  42. // the token is consumed and cannot be used to resume again.
  43. func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
  44. if len(token) != 2*utils.SecretTokenLength {
  45. return
  46. }
  47. rm.RLock()
  48. defer rm.RUnlock()
  49. id := token[:utils.SecretTokenLength]
  50. pair, ok := rm.resumeIDtoCreds[id]
  51. if ok {
  52. if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
  53. // disallow resume of an unregistered client; this prevents the use of
  54. // resume as an auth bypass
  55. if pair.client.Registered() {
  56. // consume the token, ensuring that at most one resume can succeed
  57. delete(rm.resumeIDtoCreds, id)
  58. return pair.client
  59. }
  60. }
  61. }
  62. return
  63. }
  64. // Delete stops tracking a client's resume token.
  65. func (rm *ResumeManager) Delete(client *Client) {
  66. rm.Lock()
  67. defer rm.Unlock()
  68. currentID := client.ResumeID()
  69. if currentID != "" {
  70. delete(rm.resumeIDtoCreds, currentID)
  71. }
  72. }