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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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, 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.Mutex // 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, id 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, id
  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(newClient *Client, token string) (oldClient *Client, id string) {
  44. if len(token) != 2*utils.SecretTokenLength {
  45. return
  46. }
  47. rm.Lock()
  48. defer rm.Unlock()
  49. id = token[:utils.SecretTokenLength]
  50. pair, ok := rm.resumeIDtoCreds[id]
  51. if !ok {
  52. return
  53. }
  54. // disallow resume of an unregistered client; this prevents the use of
  55. // resume as an auth bypass
  56. if !pair.client.Registered() {
  57. return
  58. }
  59. if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
  60. oldClient = pair.client // success!
  61. // consume the token, ensuring that at most one resume can succeed
  62. delete(rm.resumeIDtoCreds, id)
  63. // old client is henceforth resumeable under new client's creds (possibly empty)
  64. newResumeID := newClient.ResumeID()
  65. oldClient.SetResumeID(newResumeID)
  66. if newResumeID != "" {
  67. if newResumeCreds, ok := rm.resumeIDtoCreds[newResumeID]; ok {
  68. newResumeCreds.client = oldClient
  69. rm.resumeIDtoCreds[newResumeID] = newResumeCreds
  70. }
  71. }
  72. // new client no longer "owns" newResumeID, remove the association
  73. newClient.SetResumeID("")
  74. }
  75. return
  76. }
  77. // Delete stops tracking a client's resume token.
  78. func (rm *ResumeManager) Delete(client *Client) {
  79. rm.Lock()
  80. defer rm.Unlock()
  81. currentID := client.ResumeID()
  82. if currentID != "" {
  83. delete(rm.resumeIDtoCreds, currentID)
  84. }
  85. }