fix: handle getUser error (#3285)

This commit is contained in:
Abhineet Jain
2022-07-29 13:10:22 -04:00
committed by GitHub
parent 4658b3f0d2
commit e94fe20b6b
5 changed files with 67 additions and 20 deletions
+27 -13
View File
@@ -51,6 +51,11 @@ type OAuth2Configs struct {
Github OAuth2Config
}
const (
signedOutErrorMessage string = "You are signed out or your session has expired. Please sign in again to continue."
internalErrorMessage string = "An internal error occurred. Please try again or contact the system administrator."
)
// ExtractAPIKey requires authentication using a valid API key.
// It handles extending an API key if it comes close to expiry,
// updating the last used time in the database.
@@ -83,7 +88,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
}
if cookieValue == "" {
write(http.StatusUnauthorized, codersdk.Response{
Message: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.SessionTokenKey),
Message: signedOutErrorMessage,
Detail: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.SessionTokenKey),
})
return
}
@@ -91,7 +97,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
// APIKeys are formatted: ID-SECRET
if len(parts) != 2 {
write(http.StatusUnauthorized, codersdk.Response{
Message: fmt.Sprintf("Invalid %q cookie API key format.", codersdk.SessionTokenKey),
Message: signedOutErrorMessage,
Detail: fmt.Sprintf("Invalid %q cookie API key format.", codersdk.SessionTokenKey),
})
return
}
@@ -100,13 +107,15 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
// Ensuring key lengths are valid.
if len(keyID) != 10 {
write(http.StatusUnauthorized, codersdk.Response{
Message: fmt.Sprintf("Invalid %q cookie API key id.", codersdk.SessionTokenKey),
Message: signedOutErrorMessage,
Detail: fmt.Sprintf("Invalid %q cookie API key id.", codersdk.SessionTokenKey),
})
return
}
if len(keySecret) != 22 {
write(http.StatusUnauthorized, codersdk.Response{
Message: fmt.Sprintf("Invalid %q cookie API key secret.", codersdk.SessionTokenKey),
Message: signedOutErrorMessage,
Detail: fmt.Sprintf("Invalid %q cookie API key secret.", codersdk.SessionTokenKey),
})
return
}
@@ -114,13 +123,14 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
write(http.StatusUnauthorized, codersdk.Response{
Message: "API key is invalid.",
Message: signedOutErrorMessage,
Detail: "API key is invalid.",
})
return
}
write(http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching API key by id.",
Detail: err.Error(),
Message: internalErrorMessage,
Detail: fmt.Sprintf("Internal error fetching API key by id. %s", err.Error()),
})
return
}
@@ -129,7 +139,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
// Checking to see if the secret is valid.
if subtle.ConstantTimeCompare(key.HashedSecret, hashed[:]) != 1 {
write(http.StatusUnauthorized, codersdk.Response{
Message: "API key secret is invalid.",
Message: signedOutErrorMessage,
Detail: "API key secret is invalid.",
})
return
}
@@ -146,7 +157,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
oauthConfig = oauth.Github
default:
write(http.StatusInternalServerError, codersdk.Response{
Message: fmt.Sprintf("Unexpected authentication type %q.", key.LoginType),
Message: internalErrorMessage,
Detail: fmt.Sprintf("Unexpected authentication type %q.", key.LoginType),
})
return
}
@@ -174,7 +186,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
// Checking if the key is expired.
if key.ExpiresAt.Before(now) {
write(http.StatusUnauthorized, codersdk.Response{
Message: fmt.Sprintf("API key expired at %q.", key.ExpiresAt.String()),
Message: signedOutErrorMessage,
Detail: fmt.Sprintf("API key expired at %q.", key.ExpiresAt.String()),
})
return
}
@@ -216,7 +229,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
})
if err != nil {
write(http.StatusInternalServerError, codersdk.Response{
Message: fmt.Sprintf("API key couldn't update: %s.", err.Error()),
Message: internalErrorMessage,
Detail: fmt.Sprintf("API key couldn't update: %s.", err.Error()),
})
return
}
@@ -228,8 +242,8 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
roles, err := db.GetAuthorizationUserRoles(r.Context(), key.UserID)
if err != nil {
write(http.StatusUnauthorized, codersdk.Response{
Message: "Internal error fetching user's roles.",
Detail: err.Error(),
Message: internalErrorMessage,
Detail: fmt.Sprintf("Internal error fetching user's roles. %s", err.Error()),
})
return
}
@@ -13,9 +13,10 @@ export const RequireAuth: React.FC<RequireAuthProps> = ({ children }) => {
const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService)
const location = useLocation()
const navigateTo = location.pathname === "/" ? "/login" : embedRedirect(location.pathname)
const isHomePage = location.pathname === "/"
const navigateTo = isHomePage ? "/login" : embedRedirect(location.pathname)
if (authState.matches("signedOut")) {
return <Navigate to={navigateTo} />
return <Navigate to={navigateTo} state={{ isRedirect: !isHomePage }} />
} else if (authState.hasTag("loading")) {
return <FullScreenLoader />
} else {
@@ -51,6 +51,17 @@ WithLoginError.args = {
},
}
export const WithGetUserError = Template.bind({})
WithGetUserError.args = {
...SignedOut.args,
loginErrors: {
[LoginErrors.GET_USER_ERROR]: makeMockApiError({
message: "You are logged out. Please log in to continue.",
detail: "API Key is invalid.",
}),
},
}
export const WithCheckPermissionsError = Template.bind({})
WithCheckPermissionsError.args = {
...SignedOut.args,
@@ -70,6 +81,18 @@ WithAuthMethodsError.args = {
},
}
export const WithGetUserAndAuthMethodsError = Template.bind({})
WithGetUserAndAuthMethodsError.args = {
...SignedOut.args,
loginErrors: {
[LoginErrors.GET_USER_ERROR]: makeMockApiError({
message: "You are logged out. Please log in to continue.",
detail: "API Key is invalid.",
}),
[LoginErrors.GET_METHODS_ERROR]: new Error("Failed to fetch auth methods"),
},
}
export const WithGithub = Template.bind({})
WithGithub.args = {
...SignedOut.args,
@@ -25,6 +25,7 @@ interface BuiltInAuthFormValues {
export enum LoginErrors {
AUTH_ERROR = "authError",
GET_USER_ERROR = "getUserError",
CHECK_PERMISSIONS_ERROR = "checkPermissionsError",
GET_METHODS_ERROR = "getMethodsError",
}
@@ -36,6 +37,7 @@ export const Language = {
emailRequired: "Please enter an email address.",
errorMessages: {
[LoginErrors.AUTH_ERROR]: "Incorrect email or password.",
[LoginErrors.GET_USER_ERROR]: "Failed to fetch user details.",
[LoginErrors.CHECK_PERMISSIONS_ERROR]: "Unable to fetch user permissions.",
[LoginErrors.GET_METHODS_ERROR]: "Unable to fetch auth methods.",
},
+12 -5
View File
@@ -4,7 +4,7 @@ import React, { useContext } from "react"
import { Helmet } from "react-helmet"
import { Navigate, useLocation } from "react-router-dom"
import { Footer } from "../../components/Footer/Footer"
import { SignInForm } from "../../components/SignInForm/SignInForm"
import { LoginErrors, SignInForm } from "../../components/SignInForm/SignInForm"
import { pageTitle } from "../../util/page"
import { retrieveRedirect } from "../../util/redirect"
import { XServiceContext } from "../../xServices/StateContext"
@@ -28,6 +28,10 @@ export const useStyles = makeStyles((theme) => ({
},
}))
interface LocationState {
isRedirect: boolean
}
export const LoginPage: React.FC = () => {
const styles = useStyles()
const location = useLocation()
@@ -35,12 +39,14 @@ export const LoginPage: React.FC = () => {
const [authState, authSend] = useActor(xServices.authXService)
const isLoading = authState.hasTag("loading")
const redirectTo = retrieveRedirect(location.search)
const locationState = location.state ? (location.state as LocationState) : null
const isRedirected = locationState ? locationState.isRedirect : false
const onSubmit = async ({ email, password }: { email: string; password: string }) => {
authSend({ type: "SIGN_IN", email, password })
}
const { authError, checkPermissionsError, getMethodsError } = authState.context
const { authError, getUserError, checkPermissionsError, getMethodsError } = authState.context
if (authState.matches("signedIn")) {
return <Navigate to={redirectTo} replace />
@@ -57,9 +63,10 @@ export const LoginPage: React.FC = () => {
redirectTo={redirectTo}
isLoading={isLoading}
loginErrors={{
authError,
checkPermissionsError,
getMethodsError,
[LoginErrors.AUTH_ERROR]: authError,
[LoginErrors.GET_USER_ERROR]: isRedirected ? getUserError : null,
[LoginErrors.CHECK_PERMISSIONS_ERROR]: checkPermissionsError,
[LoginErrors.GET_METHODS_ERROR]: getMethodsError,
}}
onSubmit={onSubmit}
/>