package auth import ( "context" "crypto/tls" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/gookit/goutil/dump" "github.com/zeevdiukman/go-helper" "github.com/zeevdiukman/zprox/internal/config" "github.com/zeevdiukman/zprox/internal/session" "golang.org/x/oauth2" ) func CallbackHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := config.Data.Context tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } insecureClient := &http.Client{Transport: tr} ctx = context.WithValue(ctx, oauth2.HTTPClient, insecureClient) provider, err := oidc.NewProvider(ctx, authConfig.OpenID.EndPoints.Issuer) if err != nil { panic(err) } oidcConf := &oidc.Config{ ClientID: authConfig.OpenID.ClientID, } verifier := provider.Verifier(oidcConf) code := r.FormValue("code") state := r.FormValue("state") expectedState := session.Manager.GetString(r.Context(), "state") if state != "" && state != expectedState { dump.P("Wrong nonce") return } // nonce := r.FormValue("nonce") if code == "" { dump.P("No code provided") return } if state == "" { dump.P("No state provided") return } endPoint := oauth2.Endpoint{ AuthURL: authConfig.OpenID.EndPoints.AuthURL, TokenURL: authConfig.OpenID.EndPoints.TokenURL, } conf := &oauth2.Config{ ClientID: authConfig.OpenID.ClientID, ClientSecret: authConfig.OpenID.ClientSecret, RedirectURL: authConfig.OpenID.EndPoints.RedirectURI, Scopes: []string{"openid"}, Endpoint: endPoint, } oauth2Token, err := conf.Exchange(r.Context(), code) if err != nil { dump.Println(err.Error()) } jwksURL := authConfig.OpenID.EndPoints.JwksURI rsaPublicKey, err := FetchKeycloakPublicKey(oauth2Token, jwksURL) if err != nil { dump.Println("Error fetching public key:", err) // http.Redirect(w, r, originalPath, http.StatusTemporaryRedirect) status := http.StatusUnauthorized statusText := http.StatusText(status) w.WriteHeader(status) w.Write([]byte(statusText)) return } originalPath := session.Manager.GetString(r.Context(), "original_path") nonce := session.Manager.GetString(r.Context(), "nonce") rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError) return } idToken, err := verifier.Verify(r.Context(), rawIDToken) if err != nil { http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) return } if nonce != idToken.Nonce { dump.P("ID token nonce is not as expected") return } var claimsRaw json.RawMessage if err := idToken.Claims(&claimsRaw); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var claims []byte claimsRaw.UnmarshalJSON(claims) dump.P(claims) session.Manager.RenewToken(r.Context()) session.Manager.Put(r.Context(), "oauth2_token", oauth2Token) session.Manager.Put(r.Context(), "jwks_public_key", rsaPublicKey) ////////////////////////////////////////// // oauth2Token.SetAuthHeader(r) //????????? ////////////////////////////////////////// http.Redirect(w, r, originalPath, http.StatusTemporaryRedirect) return } } func LoginHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Add("Access-Control-Allow-Headers", "*") w.Header().Add("Access-Control-Allow-Credentials", "true") w.Header().Add("type", "application/json") b, err := io.ReadAll(r.Body) if err != nil { log.Fatalf("Error reading body: %v", err) } defer r.Body.Close() data := map[string]string{} json.Unmarshal(b, &data) username := data["username"] password := data["password"] kcData := url.Values{} kcData.Set("grant_type", "password") kcData.Set("client_id", authConfig.OpenID.ClientID) kcData.Set("client_secret", authConfig.OpenID.ClientSecret) kcData.Set("username", username) kcData.Set("password", password) client := helper.HttpClientWithSkipVerify() clientReq, err := http.NewRequest("POST", authConfig.OpenID.EndPoints.TokenURL, strings.NewReader(kcData.Encode())) if err != nil { fmt.Println("Error creating request:", err) return } clientReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := client.Do(clientReq) if err != nil { fmt.Println("Error sending request:", err) return } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response:", err) return } if resp.StatusCode != http.StatusOK { fmt.Printf("Error: Status code %d, Response: %s\n", resp.StatusCode, string(respBody)) return } var tokenResponse TokenResponse err = json.Unmarshal(respBody, &tokenResponse) if err != nil { fmt.Println("Error unmarshaling JSON:", err) return } } } func LogoutHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, ok := session.Manager.Get(r.Context(), "oauth2_token").(*oauth2.Token); !ok { // if !oauth2Token.Valid() { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(http.StatusText(http.StatusUnauthorized))) return // } } u, _ := url.Parse(authConfig.OpenID.EndPoints.LogoutURL) // Set query parameters // q := u.Query() // q.Add("client_id", url.QueryEscape(authConfig.OpenID.ClientID)) // q.Add("redirect_uri", url.QueryEscape(authConfig.OpenID.PostLogoutRedirectUri)) // // q.Add("state", ) // // q.Set("redirect_uri", url.QueryEscape(authConfig.OpenID.PostLogoutRedirectUri)) // q.Add("post_logout_redirect_uri", url.QueryEscape("https://app.z.com/auth/logout")) // u.RawQuery = q.Encode() // // Redirect to the logout URL // err = session.Manager.RenewToken(r.Context()) // if err != nil { // log.Println("Error RenewToken session:", err) // http.Error(w, "Internal Server Error", http.StatusInternalServerError) // return // } // http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) // } else { // w.WriteHeader(http.StatusUnauthorized) // w.Write([]byte(http.StatusText(http.StatusUnauthorized))) // return // } // err := session.Manager.Clear(r.Context()) // if err != nil { // log.Println("Error Clearing session:", err) // http.Error(w, "Internal Server Error", http.StatusInternalServerError) // return // } // session.Manager.Remove(r.Context(), "oauth2_token") session.Manager.Clear(r.Context()) session.Manager.RenewToken(r.Context()) http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) } } type ContextKey string func PostLogoutHandler(authConfig *config.Auth, routerData *config.Router) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // err := session.Manager.RenewToken(r.Context()) // if err != nil { // log.Println("Error RenewToken session:", err) // http.Error(w, "Internal Server Error", http.StatusInternalServerError) // return // } // r = r.WithContext(config.Data.Context) err := session.Manager.Clear(r.Context()) if err != nil { log.Println("Error Clearing session:", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // dump.P(session.Manager.Commit(r.Context())) // ctx := context.WithValue(r.Context(), ContextKey("auth"), true) // r = r.WithContext(ctx) http.Redirect(w, r, authConfig.OpenID.EndPoints.LogoutURL, http.StatusTemporaryRedirect) } }