package handler
import (
"html/template"
"net/http"
"net/url"
"os"
"strings"
"github.com/gin-gonic/gin"
"github.com/warmbly/warmbly/internal/config"
)
// callbackPage renders a tiny HTML page that hands the OAuth code + state
// back to the opening window via postMessage and then closes itself.
// The opener (the SPA) is expected to POST the code/state to
// /emails/onboarding/oauth/finish with the user's bearer token.
//
// Without an opener (the native app's ASWebAuthenticationSession, which has
// no popup parent) it instead redirects to the app's warmbly:// scheme; the
// session intercepts that navigation and the app calls oauth/finish itself.
//
// We keep this on the API rather than the SPA so that the provider's
// registered redirect_uri stays under our control and survives front-end
// reshuffles.
var callbackPage = template.Must(template.New("oauth-cb").Parse(`
Connecting…
{{.Status}}
{{if .Error}}
{{.Error}}
{{end}}
`))
type callbackData struct {
Provider string
Code string
State string
Error string
Status string
AppOrigin string
}
// callbackTargetOrigin is the postMessage target the authorization code is
// handed to. An empty value makes the bridge fall back to "*", which posts the
// code to whatever origin the opener happens to have, so derive it from APP_URL
// when APP_ORIGIN is unset rather than leaving a wildcard on a stock install.
// APP_ORIGIN stays the explicit override for a dashboard served somewhere other
// than APP_URL.
func callbackTargetOrigin() string {
if v := strings.TrimSpace(os.Getenv("APP_ORIGIN")); v != "" {
return v
}
u, err := url.Parse(config.AppBaseURL())
if err != nil || u.Scheme == "" || u.Host == "" {
return ""
}
return u.Scheme + "://" + u.Host
}
func (h *Handler) EmailOAuthCallbackGmail(c *gin.Context) {
renderOAuthCallback(c, "gmail")
}
func (h *Handler) EmailOAuthCallbackOutlook(c *gin.Context) {
renderOAuthCallback(c, "outlook")
}
func renderOAuthCallback(c *gin.Context, provider string) {
code := c.Query("code")
state := c.Query("state")
providerErr := c.Query("error")
data := callbackData{
Provider: provider,
Code: code,
State: state,
Error: providerErr,
Status: "Connecting your mailbox… this window will close.",
AppOrigin: callbackTargetOrigin(),
}
if providerErr != "" {
data.Status = "Connection cancelled."
} else if code == "" || state == "" {
data.Error = "missing_code_or_state"
data.Status = "Connection cancelled."
}
c.Header("Content-Type", "text/html; charset=utf-8")
c.Status(http.StatusOK)
_ = callbackPage.Execute(c.Writer, data)
}