Last active
June 19, 2023 16:03
-
-
Save gmlewis/26ddbb2f90eb563415f19b9e13f0c8bb to your computer and use it in GitHub Desktop.
Mechanism to perform OAuth2 dance (e.g. with Keycloak) in a wails production build
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2023 Omics Data Automation, Inc. - All Rights Reserved | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
package main | |
import ( | |
"io" | |
"log" | |
"net/http" | |
"strings" | |
) | |
const ( | |
port = ":5173" | |
) | |
// This callback server is started in a goroutine for serving | |
// the files from the "assets" embed.FS in "main.go". | |
// | |
// It is only started in a production build by checking on startup. | |
// For example, main.go looks like this: | |
// | |
// // The following are provided by injection from -ldflags: | |
// var ( | |
// Standalone string // cannot be bool for injection to work | |
// ) | |
// | |
// func main() { | |
// if Standalone == "true" { | |
// go startCallbackServer() | |
// } | |
// ... | |
// } | |
func startCallbackServer() { | |
http.HandleFunc("/", jwtHandler) | |
srv := &http.Server{Addr: port} | |
// always returns error. ErrServerClosed on graceful close | |
if err := srv.ListenAndServe(); err != http.ErrServerClosed { | |
log.Fatalf("ListenAndServe: %v", err) | |
} | |
} | |
// jwtHandler serves the app files in order to handle redirects | |
// from OAuth2 dances (which the app itself then handles). | |
// The `injectEnvVars` function used below simply returns a string | |
// that embeds JavaScript before the rest of the file it is serving. | |
// A string like `window.configs = { "ENV_VAR_1": "value1", ...};` | |
// is an easy way to inject env vars into the app. Note that | |
// the trailing ';' is critical for not messing up the rest of the | |
// JavaScript file. | |
func jwtHandler(w http.ResponseWriter, r *http.Request) { | |
// log.Printf("jwtHandler: %v %v", r.Method, r.URL.Path) | |
path := r.URL.Path | |
if path == "/" { | |
// log.Printf("query: %#v", r.URL.Query()) | |
path = "/index.html" | |
} | |
var mimeType string | |
switch { | |
case strings.HasSuffix(path, ".css"): | |
mimeType = "text/css" | |
case strings.HasSuffix(path, ".html"): | |
mimeType = "text/html" | |
case strings.HasSuffix(path, ".js"): | |
mimeType = "text/javascript" | |
case strings.HasSuffix(path, ".ttf"): | |
mimeType = "font/ttf" | |
case strings.HasSuffix(path, ".ico"): | |
mimeType = "image/vnd.microsoft.icon" | |
default: | |
log.Printf("Unknown MIME type for path %q", path) | |
w.WriteHeader(http.StatusNotFound) | |
w.Write([]byte("not found")) | |
return | |
} | |
body, err := assets.Open("frontend/dist" + path) | |
if err != nil { | |
log.Printf("Unable to open %q: %v", path, err) | |
w.WriteHeader(http.StatusNotFound) | |
w.Write([]byte("not found")) | |
return | |
} | |
defer body.Close() | |
w.Header().Add("Content-Type", mimeType) | |
if mimeType == "text/javascript" { // This must happen after the header is added. | |
if _, err := w.Write([]byte(injectEnvVars())); err != nil { | |
log.Printf("ERROR: Unable to inject env vars: %v", err) | |
} | |
} | |
if _, err := io.Copy(w, body); err != nil { | |
log.Printf("ERROR: io.Copy: %v", err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment