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.
 
 
 

124 lines
3.5 KiB

  1. package handlers
  2. // Inspired by node.js' Connect library implementation of the basicAuth middleware.
  3. // https://github.com/senchalabs/connect
  4. import (
  5. "bytes"
  6. "encoding/base64"
  7. "fmt"
  8. "net/http"
  9. "strings"
  10. )
  11. // Internal writer that keeps track of the currently authenticated user.
  12. type userResponseWriter struct {
  13. http.ResponseWriter
  14. user interface{}
  15. userName string
  16. }
  17. // Implement the WrapWriter interface.
  18. func (this *userResponseWriter) WrappedWriter() http.ResponseWriter {
  19. return this.ResponseWriter
  20. }
  21. // Writes an unauthorized response to the client, specifying the expected authentication
  22. // information.
  23. func Unauthorized(w http.ResponseWriter, realm string) {
  24. w.Header().Set("Www-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
  25. w.WriteHeader(http.StatusUnauthorized)
  26. w.Write([]byte("Unauthorized"))
  27. }
  28. // Writes a bad request response to the client, with an optional message.
  29. func BadRequest(w http.ResponseWriter, msg string) {
  30. w.WriteHeader(http.StatusBadRequest)
  31. if msg == "" {
  32. msg = "Bad Request"
  33. }
  34. w.Write([]byte(msg))
  35. }
  36. // BasicAuthHandlerFunc is the same as BasicAuthHandler, it is just a convenience
  37. // signature that accepts a func(http.ResponseWriter, *http.Request) instead of
  38. // a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
  39. func BasicAuthHandlerFunc(h http.HandlerFunc,
  40. authFn func(string, string) (interface{}, bool), realm string) http.HandlerFunc {
  41. return BasicAuthHandler(h, authFn, realm)
  42. }
  43. // Returns a Basic Authentication handler, protecting the wrapped handler from
  44. // being accessed if the authentication function is not successful.
  45. func BasicAuthHandler(h http.Handler,
  46. authFn func(string, string) (interface{}, bool), realm string) http.HandlerFunc {
  47. if realm == "" {
  48. realm = "Authorization Required"
  49. }
  50. return func(w http.ResponseWriter, r *http.Request) {
  51. // Self-awareness
  52. if _, ok := GetUser(w); ok {
  53. h.ServeHTTP(w, r)
  54. return
  55. }
  56. authInfo := r.Header.Get("Authorization")
  57. if authInfo == "" {
  58. // No authorization info, return 401
  59. Unauthorized(w, realm)
  60. return
  61. }
  62. parts := strings.Split(authInfo, " ")
  63. if len(parts) != 2 {
  64. BadRequest(w, "Bad authorization header")
  65. return
  66. }
  67. scheme := parts[0]
  68. creds, err := base64.StdEncoding.DecodeString(parts[1])
  69. if err != nil {
  70. BadRequest(w, "Bad credentials encoding")
  71. return
  72. }
  73. index := bytes.Index(creds, []byte(":"))
  74. if scheme != "Basic" || index < 0 {
  75. BadRequest(w, "Bad authorization header")
  76. return
  77. }
  78. user, pwd := string(creds[:index]), string(creds[index+1:])
  79. udata, ok := authFn(user, pwd)
  80. if ok {
  81. // Save user data and continue
  82. uw := &userResponseWriter{w, udata, user}
  83. h.ServeHTTP(uw, r)
  84. } else {
  85. Unauthorized(w, realm)
  86. }
  87. }
  88. }
  89. // Return the currently authenticated user. This is the same data that was returned
  90. // by the authentication function passed to BasicAuthHandler.
  91. func GetUser(w http.ResponseWriter) (interface{}, bool) {
  92. usr, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
  93. _, ok := tst.(*userResponseWriter)
  94. return ok
  95. })
  96. if ok {
  97. return usr.(*userResponseWriter).user, true
  98. }
  99. return nil, false
  100. }
  101. // Return the currently authenticated user name. This is the user name that was
  102. // authenticated for the current request.
  103. func GetUserName(w http.ResponseWriter) (string, bool) {
  104. usr, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
  105. _, ok := tst.(*userResponseWriter)
  106. return ok
  107. })
  108. if ok {
  109. return usr.(*userResponseWriter).userName, true
  110. }
  111. return "", false
  112. }