package basicauth import ( "fmt" "net/http" "time" "github.com/kataras/iris/v12/context" ) type ( // ErrHTTPVersion is fired when Options.HTTPSOnly was enabled // and the current request is a plain http one. ErrHTTPVersion struct{} // ErrCredentialsForbidden is fired when Options.MaxTries have been consumed // by the user and the client is forbidden to retry at least for "Age" time. ErrCredentialsForbidden struct { Username string Password string Tries int Age time.Duration } // ErrCredentialsMissing is fired when the authorization header is empty or malformed. ErrCredentialsMissing struct { Header string AuthenticateHeader string AuthenticateHeaderValue string Code int } // ErrCredentialsInvalid is fired when the user input does not match with an existing user. ErrCredentialsInvalid struct { Username string Password string CurrentTries int AuthenticateHeader string AuthenticateHeaderValue string Code int } // ErrCredentialsExpired is fired when the username:password combination is valid // but the memory stored user has been expired. ErrCredentialsExpired struct { Username string Password string AuthenticateHeader string AuthenticateHeaderValue string Code int } ) func (e ErrHTTPVersion) Error() string { return "http version not supported" } func (e ErrCredentialsForbidden) Error() string { return fmt.Sprintf("credentials: forbidden <%s:%s> for <%s> after <%d> attempts", e.Username, e.Password, e.Age, e.Tries) } func (e ErrCredentialsMissing) Error() string { if e.Header != "" { return fmt.Sprintf("credentials: malformed <%s>", e.Header) } return "empty credentials" } func (e ErrCredentialsInvalid) Error() string { return fmt.Sprintf("credentials: invalid <%s:%s> current tries <%d>", e.Username, e.Password, e.CurrentTries) } func (e ErrCredentialsExpired) Error() string { return fmt.Sprintf("credentials: expired <%s:%s>", e.Username, e.Password) } // DefaultErrorHandler is the default error handler for the Options.ErrorHandler field. func DefaultErrorHandler(ctx *context.Context, err error) { switch e := err.(type) { case ErrHTTPVersion: ctx.StopWithStatus(http.StatusHTTPVersionNotSupported) case ErrCredentialsForbidden: // If a (proxy) server receives valid credentials that are inadequate to access a given resource, // the server should respond with the 403 Forbidden status code. // Unlike 401 Unauthorized or 407 Proxy Authentication Required, authentication is impossible for this user. ctx.StopWithStatus(http.StatusForbidden) case ErrCredentialsMissing: unauthorize(ctx, e.AuthenticateHeader, e.AuthenticateHeaderValue, e.Code) case ErrCredentialsInvalid: unauthorize(ctx, e.AuthenticateHeader, e.AuthenticateHeaderValue, e.Code) case ErrCredentialsExpired: unauthorize(ctx, e.AuthenticateHeader, e.AuthenticateHeaderValue, e.Code) default: // This will never happen. ctx.StopWithText(http.StatusInternalServerError, "unknown error: %v", err) } } // unauthorize sends a 401 status code (or 407 if Proxy was set to true) // which client should catch and prompt for username:password credentials. func unauthorize(ctx *context.Context, authHeader, authHeaderValue string, code int) { ctx.Header(authHeader, authHeaderValue) ctx.StopWithStatus(code) }