fediverse.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package fediverse
  2. import (
  3. "crypto/rand"
  4. "errors"
  5. "io"
  6. "strings"
  7. "sync"
  8. "time"
  9. log "github.com/sirupsen/logrus"
  10. )
  11. // OTPRegistration represents a single OTP request.
  12. type OTPRegistration struct {
  13. Timestamp time.Time
  14. UserID string
  15. UserDisplayName string
  16. Code string
  17. Account string
  18. }
  19. // Key by access token to limit one OTP request for a person
  20. // to be active at a time.
  21. var (
  22. pendingAuthRequests = make(map[string]OTPRegistration)
  23. lock = sync.Mutex{}
  24. )
  25. const (
  26. registrationTimeout = time.Minute * 10
  27. maxPendingRequests = 1000
  28. )
  29. func init() {
  30. go setupExpiredRequestPruner()
  31. }
  32. // Clear out any pending requests that have been pending for greater than
  33. // the specified timeout value.
  34. func setupExpiredRequestPruner() {
  35. pruneExpiredRequestsTimer := time.NewTicker(registrationTimeout)
  36. for range pruneExpiredRequestsTimer.C {
  37. lock.Lock()
  38. log.Debugln("Pruning expired OTP requests.")
  39. for k, v := range pendingAuthRequests {
  40. if time.Since(v.Timestamp) > registrationTimeout {
  41. delete(pendingAuthRequests, k)
  42. }
  43. }
  44. lock.Unlock()
  45. }
  46. }
  47. // RegisterFediverseOTP will start the OTP flow for a user, creating a new
  48. // code and returning it to be sent to a destination.
  49. func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string) (OTPRegistration, bool, error) {
  50. request, requestExists := pendingAuthRequests[accessToken]
  51. // If a request is already registered and has not expired then return that
  52. // existing request.
  53. if requestExists && time.Since(request.Timestamp) < registrationTimeout {
  54. return request, false, nil
  55. }
  56. lock.Lock()
  57. defer lock.Unlock()
  58. if len(pendingAuthRequests)+1 > maxPendingRequests {
  59. return request, false, errors.New("Please try again later. Too many pending requests.")
  60. }
  61. code, _ := createCode()
  62. r := OTPRegistration{
  63. Code: code,
  64. UserID: userID,
  65. UserDisplayName: userDisplayName,
  66. Account: strings.ToLower(account),
  67. Timestamp: time.Now(),
  68. }
  69. pendingAuthRequests[accessToken] = r
  70. return r, true, nil
  71. }
  72. // ValidateFediverseOTP will verify a OTP code for a auth request.
  73. func ValidateFediverseOTP(accessToken, code string) (bool, *OTPRegistration) {
  74. request, ok := pendingAuthRequests[accessToken]
  75. if !ok || request.Code != code || time.Since(request.Timestamp) > registrationTimeout {
  76. return false, nil
  77. }
  78. lock.Lock()
  79. defer lock.Unlock()
  80. delete(pendingAuthRequests, accessToken)
  81. return true, &request
  82. }
  83. func createCode() (string, error) {
  84. table := [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
  85. digits := 6
  86. b := make([]byte, digits)
  87. n, err := io.ReadAtLeast(rand.Reader, b, digits)
  88. if n != digits {
  89. return "", err
  90. }
  91. for i := 0; i < len(b); i++ {
  92. b[i] = table[int(b[i])%len(table)]
  93. }
  94. return string(b), nil
  95. }