Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: log out and redirect user when converting to oidc #8347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,12 +631,18 @@ func New(options *Options) *API {
r.Post("/login", api.postLogin)
r.Route("/oauth2", func(r chi.Router) {
r.Route("/github", func(r chi.Router) {
r.Use(httpmw.ExtractOAuth2(options.GithubOAuth2Config, options.HTTPClient, nil))
r.Use(
httpmw.ExtractOAuth2(options.GithubOAuth2Config, options.HTTPClient, nil),
apiKeyMiddlewareOptional,
)
r.Get("/callback", api.userOAuth2Github)
})
})
r.Route("/oidc/callback", func(r chi.Router) {
r.Use(httpmw.ExtractOAuth2(options.OIDCConfig, options.HTTPClient, oidcAuthURLParams))
r.Use(
httpmw.ExtractOAuth2(options.OIDCConfig, options.HTTPClient, oidcAuthURLParams),
apiKeyMiddlewareOptional,
)
r.Get("/", api.userOIDC)
})
})
Expand Down
48 changes: 38 additions & 10 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
cookies []*http.Cookie
)

var isConvertLoginType bool
err := api.Database.InTx(func(tx database.Store) error {
var (
link database.UserLink
Expand All @@ -1116,6 +1117,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
return err
}
params.User = user
isConvertLoginType = true
}

if user.ID == uuid.Nil && !params.AllowSignups {
Expand Down Expand Up @@ -1284,18 +1286,44 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
return nil, database.APIKey{}, xerrors.Errorf("in tx: %w", err)
}

//nolint:gocritic
cookie, key, err := api.createAPIKey(dbauthz.AsSystemRestricted(ctx), apikey.CreateParams{
UserID: user.ID,
LoginType: params.LoginType,
DeploymentValues: api.DeploymentValues,
RemoteAddr: r.RemoteAddr,
})
if err != nil {
return nil, database.APIKey{}, xerrors.Errorf("create API key: %w", err)
var key database.APIKey
if oldKey, ok := httpmw.APIKeyOptional(r); ok && isConvertLoginType {
// If this is a convert login type, and it succeeds, then delete the old
// session. Force the user to log back in.
err := api.Database.DeleteAPIKeyByID(r.Context(), oldKey.ID)
if err != nil {
// Do not block this login if we fail to delete the old API key.
// Just delete the cookie and continue.
api.Logger.Warn(r.Context(), "failed to delete old API key in convert to oidc",
slog.Error(err),
slog.F("old_api_key_id", oldKey.ID),
slog.F("user_id", user.ID),
)
}
cookies = append(cookies, &http.Cookie{
Name: codersdk.SessionTokenCookie,
Path: "/",
MaxAge: -1,
Secure: api.SecureAuthCookie,
HttpOnly: true,
})
key = oldKey
} else {
//nolint:gocritic
cookie, newKey, err := api.createAPIKey(dbauthz.AsSystemRestricted(ctx), apikey.CreateParams{
UserID: user.ID,
LoginType: params.LoginType,
DeploymentValues: api.DeploymentValues,
RemoteAddr: r.RemoteAddr,
})
if err != nil {
return nil, database.APIKey{}, xerrors.Errorf("create API key: %w", err)
}
cookies = append(cookies, cookie)
key = *newKey
}

return append(cookies, cookie), *key, nil
return cookies, key, nil
}

// convertUserToOauth will convert a user from password base loginType to
Expand Down
11 changes: 9 additions & 2 deletions site/src/components/SignInForm/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const useStyles = makeStyles((theme) => ({
fontWeight: 600,
},
},
error: {
alert: {
marginBottom: theme.spacing(4),
},
divider: {
Expand Down Expand Up @@ -69,6 +69,7 @@ export interface SignInFormProps {
isSigningIn: boolean
redirectTo: string
error?: unknown
info?: string
authMethods?: AuthMethods
onSubmit: (credentials: { email: string; password: string }) => void
// initialTouched is only used for testing the error state of the form.
Expand All @@ -80,6 +81,7 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
redirectTo,
isSigningIn,
error,
info,
onSubmit,
initialTouched,
}) => {
Expand All @@ -100,10 +102,15 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
<strong>{commonTranslation.t("coder")}</strong>
</h1>
<Maybe condition={error !== undefined}>
<div className={styles.error}>
<div className={styles.alert}>
<ErrorAlert error={error} />
</div>
</Maybe>
<Maybe condition={Boolean(info) && info !== "" && error === undefined}>
<div className={styles.alert}>
<Alert severity="info">{info}</Alert>
</div>
</Maybe>
<Maybe condition={passwordEnabled && showPasswordAuth}>
<PasswordSignInForm
onSubmit={onSubmit}
Expand Down
4 changes: 4 additions & 0 deletions site/src/pages/LoginPage/LoginPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
const { error } = context
const data = context.data as UnauthenticatedData
const styles = useStyles()
// This allows messages to be displayed at the top of the sign in form.
// Helpful for any redirects that want to inform the user of something.
const info = new URLSearchParams(location.search).get("info") || undefined

return isLoading ? (
<FullScreenLoader />
Expand All @@ -37,6 +40,7 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
redirectTo={redirectTo}
isSigningIn={isSigningIn}
error={error}
info={info}
onSubmit={onSignIn}
/>
<footer className={styles.footer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import Box from "@mui/material/Box"
import GitHubIcon from "@mui/icons-material/GitHub"
import KeyIcon from "@mui/icons-material/VpnKey"
import Button from "@mui/material/Button"
import { useLocation } from "react-router-dom"
import { retrieveRedirect } from "utils/redirect"
import Typography from "@mui/material/Typography"
import { convertToOAUTH } from "api/api"
import { AuthMethods, LoginType, UserLoginType } from "api/typesGenerated"
Expand All @@ -27,19 +25,31 @@ type LoginTypeConfirmation =
selectedType: LoginType
}

export const redirectToOIDCAuth = (stateString: string, redirectTo: string) => {
window.location.href = `/api/v2/users/oidc/callback?oidc_merge_state=${stateString}&redirect=${redirectTo}`
export const redirectToOIDCAuth = (
toType: string,
stateString: string,
redirectTo: string,
) => {
window.location.href = `/api/v2/users/${toType}/callback?oidc_merge_state=${stateString}&redirect=${redirectTo}`
}

export const useSingleSignOnSection = () => {
const location = useLocation()
const redirectTo = retrieveRedirect(location.search)
const [loginTypeConfirmation, setLoginTypeConfirmation] =
useState<LoginTypeConfirmation>({ open: false, selectedType: undefined })

const mutation = useMutation(convertToOAUTH, {
onSuccess: (data) => {
redirectToOIDCAuth(data.state_string, encodeURIComponent(redirectTo))
const loginTypeMsg =
data.to_type === "github" ? "Github" : "OpenID Connect"
redirectToOIDCAuth(
data.to_type,
data.state_string,
// The redirect on success should be back to the login page with a nice message.
// The user should be logged out if this worked.
encodeURIComponent(
`/login?info=Login type has been changed to ${loginTypeMsg}. Log in again using the new method.`,
),
)
},
})

Expand Down