Skip to content

Commit

Permalink
Handle unified mfa response to create privileged token.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joerger committed Dec 3, 2024
1 parent 518f3ef commit 7a41d14
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 16 deletions.
12 changes: 12 additions & 0 deletions lib/web/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/gravitational/teleport/api/mfa"
"github.com/gravitational/teleport/api/types"
wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/web/ui"
)
Expand Down Expand Up @@ -251,10 +252,15 @@ func deleteUser(r *http.Request, params httprouter.Params, m userAPIGetter, user
}

type privilegeTokenRequest struct {
// TODO(Joerger): DELETE IN v19.0.0 in favor of ExistingMFAResponse
// SecondFactorToken is the totp code.
SecondFactorToken string `json:"secondFactorToken"`
// TODO(Joerger): DELETE IN v19.0.0 in favor of ExistingMFAResponse
// WebauthnResponse is the response from authenticators.
WebauthnResponse *wantypes.CredentialAssertionResponse `json:"webauthnAssertionResponse"`
// ExistingMFAResponse is an MFA challenge response from an existing device.
// Not required if the user has no existing devices.
ExistingMFAResponse *client.MFAChallengeResponse `json:"existingMfaResponse"`
}

// createPrivilegeTokenHandle creates and returns a privilege token.
Expand All @@ -275,6 +281,12 @@ func (h *Handler) createPrivilegeTokenHandle(w http.ResponseWriter, r *http.Requ
protoReq.ExistingMFAResponse = &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_Webauthn{
Webauthn: wantypes.CredentialAssertionResponseToProto(req.WebauthnResponse),
}}
case req.ExistingMFAResponse != nil:
var err error
protoReq.ExistingMFAResponse, err = req.ExistingMFAResponse.GetOptionalMFAResponseProtoReq()
if err != nil {
return nil, trace.Wrap(err)
}
default:
// Can be empty, which means user did not have a second factor registered.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function useReAuthenticate(props: Props) {

setAttempt({ status: 'processing' });
auth
.createPrivilegeTokenWithTotp(secondFactorToken)
.createPrivilegeToken({ totp_code: secondFactorToken })
.then(props.onAuthenticated)
.catch(handleError);
}
Expand All @@ -65,8 +65,11 @@ export default function useReAuthenticate(props: Props) {
return;
}

// Creating privilege tokens always expects the MANAGE_DEVICES webauthn scope.
auth
.createPrivilegeTokenWithWebauthn()
.getMfaChallenge({ scope: MfaChallengeScope.MANAGE_DEVICES })
.then(auth.getMfaChallengeResponse)
.then(auth.createPrivilegeToken)
.then(props.onAuthenticated)
.catch((err: Error) => {
// This catches a webauthn frontend error that occurs on Firefox and replaces it with a more helpful error message.
Expand Down
31 changes: 17 additions & 14 deletions web/packages/teleport/src/services/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,6 @@ const auth = {
return api.put(cfg.getHeadlessSsoPath(transactionId), request);
},

createPrivilegeTokenWithTotp(secondFactorToken: string) {
return api.post(cfg.api.createPrivilegeTokenPath, { secondFactorToken });
},

// getChallenge gets an MFA challenge for the provided parameters. If is_mfa_required_req
// is provided and it is found that MFA is not required, returns null instead.
async getMfaChallenge(
Expand Down Expand Up @@ -333,20 +329,27 @@ const auth = {
);
},

// TODO(Joerger): Combine with otp endpoint.
createPrivilegeToken(existingMfaResponse: MfaChallengeResponse) {
return api.post(cfg.api.createPrivilegeTokenPath, {
existingMfaResponse,
// TODO(Joerger): DELETE IN v19.0.0
// Also provide totp/webauthn response in backwards compatible format.
secondFactorToken: existingMfaResponse.totp_code,
webauthnAssertionResponse: existingMfaResponse.webauthn_response,
});
},

// TODO(Joerger): Delete once no longer used by /e
createPrivilegeTokenWithWebauthn() {
// Creating privilege tokens always expects the MANAGE_DEVICES webauthn scope.
return auth
.getMfaChallenge({ scope: MfaChallengeScope.MANAGE_DEVICES })
.then(auth.getMfaChallengeResponse)
.then(res =>
api.post(cfg.api.createPrivilegeTokenPath, {
// TODO(Joerger): Handle non-webauthn challenges.
webauthnAssertionResponse: makeWebauthnAssertionResponse(
res.webauthn_response
),
})
);
.then(mfaResp => auth.createPrivilegeToken(mfaResp));
},

// TODO(Joerger): Delete once no longer used by /e
createPrivilegeTokenWithTotp(secondFactorToken: string) {
return api.post(cfg.api.createPrivilegeTokenPath, { secondFactorToken });
},

createRestrictedPrivilegeToken() {
Expand Down

0 comments on commit 7a41d14

Please sign in to comment.