// validRedirectURL finds the first client for which the redirect URL is valid. If found it returns the client_id of the client. func validRedirectURL(clientManager *clientmanager.ClientManager, redirectURL url.URL, clientIDs []string) (string, error) { // Find the first client with a valid redirectURL. for _, clientID := range clientIDs { metadata, err := clientManager.Metadata(clientID) if err != nil { return "", mapError(err) } if _, err := client.ValidRedirectURL(&redirectURL, metadata.RedirectURIs); err == nil { return clientID, nil } } return "", ErrorInvalidRedirectURL }
// handleVerifyEmailResendFunc will resend an email-verification email given a valid JWT for the user and a redirect URL. // This handler is meant to be wrapped in clientTokenMiddleware, so a valid // bearer token for the client is expected to be present. // The user's JWT should be in the "token" parameter and the redirect URL should // be in the "redirect_uri" param. func handleVerifyEmailResendFunc( issuerURL url.URL, srvKeysFunc func() ([]key.PublicKey, error), emailer *useremail.UserEmailer, userRepo user.UserRepo, clientManager *clientmanager.ClientManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var params struct { Token string `json:"token"` RedirectURI string `json:"redirectURI"` } err := decoder.Decode(¶ms) if err != nil { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "unable to parse body as JSON")) return } token := params.Token if token == "" { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "missing valid JWT")) return } clientID, err := getClientIDFromAuthorizedRequest(r) if err != nil { log.Errorf("Failed to extract clientID: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorInvalidRequest, "cilent could not be extracted from bearer token.")) return } cm, err := clientManager.Metadata(clientID) if err == client.ErrorNotFound { log.Errorf("No such client: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid client_id")) return } if err != nil { log.Errorf("Error getting ClientMetadata: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } noop := func() error { return nil } keysFunc := func() []key.PublicKey { keys, err := srvKeysFunc() if err != nil { log.Errorf("Error getting keys: %v", err) } return keys } jwt, err := jose.ParseJWT(token) if err != nil { log.Errorf("Failed to Parse JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "token could not be parsed")) return } verifier := oidc.NewJWTVerifier(issuerURL.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { log.Errorf("Failed to Verify JWT: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "invalid token could not be verified")) return } claims, err := jwt.Claims() if err != nil { log.Errorf("Failed to extract claims from JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid token could not be parsed")) return } sub, ok, err := claims.StringClaim("sub") if err != nil || !ok || sub == "" { log.Errorf("Failed to extract sub claim from JWT: err:%q ok:%v", err, ok) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not extract sub claim from token")) return } usr, err := userRepo.Get(nil, sub) if err != nil { if err == user.ErrorNotFound { log.Errorf("Failed to find user specified by token: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not find user")) return } log.Errorf("Failed to fetch user: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } if usr.EmailVerified { log.Errorf("User's email already verified") writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "email already verified")) return } aud, _, _ := claims.StringClaim("aud") if aud != clientID { log.Errorf("aud of token and sub of bearer token must match: %v", err) writeAPIError(w, http.StatusForbidden, newAPIError(errorAccessDenied, "JWT is from another client.")) return } redirectURLStr := params.RedirectURI if redirectURLStr == "" { log.Errorf("No redirect URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "must provide a redirect_uri")) return } redirectURL, err := url.Parse(redirectURLStr) if err != nil { log.Errorf("Unparsable URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } *redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURIs) if err != nil { switch err { case (client.ErrorInvalidRedirectURL): log.Errorf("Request provided unregistered redirect URL: %s", redirectURLStr) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return case (client.ErrorNoValidRedirectURLs): log.Errorf("There are no registered URLs for the requested client: %s", redirectURL) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } } _, err = emailer.SendEmailVerification(usr.ID, clientID, *redirectURL) if err != nil { log.Errorf("Failed to send email verification email: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } writeResponseWithBody(w, http.StatusOK, struct{}{}) } }