handler.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package authn
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/porter-dev/porter/api/server/shared"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors"
  9. "github.com/porter-dev/porter/api/types"
  10. "github.com/porter-dev/porter/internal/auth/token"
  11. )
  12. // AuthNFactory generates a middleware handler `AuthN`
  13. type AuthNFactory struct {
  14. config *shared.Config
  15. }
  16. // NewAuthNFactory returns an `AuthNFactory` that uses the passed-in server
  17. // config
  18. func NewAuthNFactory(
  19. config *shared.Config,
  20. ) *AuthNFactory {
  21. return &AuthNFactory{config}
  22. }
  23. // NewAuthenticated creates a new instance of `AuthN` that implements the http.Handler
  24. // interface.
  25. func (f *AuthNFactory) NewAuthenticated(next http.Handler) http.Handler {
  26. return &AuthN{next, f.config}
  27. }
  28. // AuthN implements the authentication middleware
  29. type AuthN struct {
  30. next http.Handler
  31. config *shared.Config
  32. }
  33. // ServeHTTP attaches an authenticated subject to the request context,
  34. // or serves a forbidden error. If authenticated, it calls the next handler.
  35. //
  36. // A token can either be issued for a specific project id or for a user. In the case
  37. // of a project id, we attach a service account to the context. In the case of a
  38. // user, we attach that user to the context.
  39. func (authn *AuthN) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  40. // first check for a bearer token
  41. tok, err := authn.getTokenFromRequest(r)
  42. // if the error is not an invalid auth error, the token was invalid, and we throw error
  43. // forbidden. If the error was an invalid auth error, we look for a cookie.
  44. if err != nil && err != errInvalidAuthHeader {
  45. authn.sendForbiddenError(err, w)
  46. return
  47. } else if err == nil && tok != nil {
  48. authn.nextWithToken(w, r, tok)
  49. return
  50. }
  51. // if the bearer token is not found, look for a request cookie
  52. session, err := authn.config.Store.Get(r, authn.config.CookieName)
  53. if err != nil {
  54. session.Values["authenticated"] = false
  55. // we attempt to save the session, but do not catch the error since we send the
  56. // forbidden error regardless
  57. session.Save(r, w)
  58. authn.sendForbiddenError(err, w)
  59. return
  60. }
  61. if auth, ok := session.Values["authenticated"].(bool); !auth || !ok {
  62. authn.sendForbiddenError(fmt.Errorf("stored cookie was not authenticated"), w)
  63. return
  64. }
  65. // read the user id in the token
  66. userID, ok := session.Values["user_id"].(uint)
  67. if !ok {
  68. authn.sendForbiddenError(fmt.Errorf("could not cast user_id to uint"), w)
  69. return
  70. }
  71. authn.nextWithUserID(w, r, userID)
  72. }
  73. // nextWithToken calls the next handler with either the service account or user corresponding
  74. // to the token set in context.
  75. func (authn *AuthN) nextWithToken(w http.ResponseWriter, r *http.Request, tok *token.Token) {
  76. // TODO: add section to get service account for server-side token
  77. // for now, we just use nextWithUser using the `iby` field for the token
  78. authn.nextWithUserID(w, r, tok.IBy)
  79. }
  80. // nextWithUserID calls the next handler with the user set in the context with key
  81. // `types.UserScope`.
  82. func (authn *AuthN) nextWithUserID(w http.ResponseWriter, r *http.Request, userID uint) {
  83. // search for the user
  84. user, err := authn.config.Repo.User().ReadUser(userID)
  85. if err != nil {
  86. authn.sendForbiddenError(fmt.Errorf("user with id %d not found in database", userID), w)
  87. return
  88. }
  89. // add the user to the context
  90. ctx := r.Context()
  91. ctx = context.WithValue(ctx, types.UserScope, user)
  92. r = r.WithContext(ctx)
  93. authn.next.ServeHTTP(w, r)
  94. }
  95. // sendForbiddenError sends a 403 Forbidden error to the end user while logging a
  96. // specific error
  97. func (authn *AuthN) sendForbiddenError(err error, w http.ResponseWriter) {
  98. reqErr := apierrors.NewErrForbidden(err)
  99. apierrors.HandleAPIError(w, authn.config.Logger, reqErr)
  100. }
  101. var errInvalidToken = fmt.Errorf("authorization header exists, but token is not valid")
  102. var errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request")
  103. // getTokenFromRequest finds an `Authorization` header of the form `Bearer <token>`,
  104. // and returns a valid token if it exists.
  105. func (authn *AuthN) getTokenFromRequest(r *http.Request) (*token.Token, error) {
  106. reqToken := r.Header.Get("Authorization")
  107. splitToken := strings.Split(reqToken, "Bearer")
  108. if len(splitToken) != 2 {
  109. return nil, errInvalidAuthHeader
  110. }
  111. reqToken = strings.TrimSpace(splitToken[1])
  112. tok, err := token.GetTokenFromEncoded(reqToken, authn.config.TokenConf)
  113. if err != nil {
  114. return nil, errInvalidToken
  115. }
  116. return tok, nil
  117. }