mirror of
https://github.com/h44z/wg-portal.git
synced 2026-08-13 11:16:19 +00:00
Fix/OIDC return url base path (#735)
* fix: build OAuth return URL from the live location, not the Vite base On base_path deployments, OAuth/OIDC login failed with a 400 "invalid return URL" before the user ever reached the IdP. LoginView and the router derived the app's runtime URL mount from import.meta.env.BASE_URL, which is Vite's build-time *asset* base. Since #711 set `base: './'` to fix relative asset loading (#710), BASE_URL is "./", so `base_path: /wg` produced the return URL https://host/wg./#/login. isValidReturnUrl() requires <base_path>/app, so every external-auth user on a base_path deployment was locked out. Derive the return URL from window.location instead: the app is mounted at {base_path}/app/ in production and at / under `npm run dev`, so the live document location is the only reliable source. Likewise drop the explicit base from createWebHashHistory(), which then defaults to `location.pathname + location.search` -- correct in every deployment. This leaves the relative asset base from #711 untouched, so #710 stays fixed, and it removes the last two readers of import.meta.env.BASE_URL under frontend/src so the asset base can no longer affect routing. Fixes #719 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dan Berg <dan@webinargeek.com> * fix: log rejected OAuth return URLs The isValidReturnUrl() rejection was the only failure branch in handleOauthInitiateGet without a slog call, and the 400 response body reveals neither what was received nor what was expected. That is what made #719 hard to diagnose. Log both at Debug level, matching the neighbouring branches. The response body is unchanged -- the URL is not echoed to the client. Also note on the base-path test that the URL shape it pins is produced by externalLogin() in frontend/src/views/LoginView.vue, so a future frontend change has a breadcrumb back to the contract. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dan Berg <dan@webinargeek.com> --------- Signed-off-by: Dan Berg <dan@webinargeek.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c8da743d0e
commit
62e7f9d8b9
@@ -6,10 +6,10 @@ import {authStore} from '@/stores/auth'
|
|||||||
import {securityStore} from '@/stores/security'
|
import {securityStore} from '@/stores/security'
|
||||||
import {notify} from "@kyvg/vue3-notification";
|
import {notify} from "@kyvg/vue3-notification";
|
||||||
|
|
||||||
const routerBase = `${WGPORTAL_BASE_PATH || ''}${import.meta.env.BASE_URL || '/'}`
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(routerBase),
|
// No base argument: createWebHashHistory() defaults to location.pathname + location.search,
|
||||||
|
// which is correct for /app/, {web.base_path}/app/ and the dev server at /.
|
||||||
|
history: createWebHashHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|||||||
@@ -83,7 +83,10 @@ const externalLogin = function (provider) {
|
|||||||
console.log("Performing external login for provider", provider.Identifier);
|
console.log("Performing external login for provider", provider.Identifier);
|
||||||
loggingIn.value = true;
|
loggingIn.value = true;
|
||||||
console.log(router.currentRoute.value);
|
console.log(router.currentRoute.value);
|
||||||
const currentUrl = new URL(`${WGPORTAL_BASE_PATH || ''}${import.meta.env.BASE_URL || '/'}`, window.location.origin);
|
// Derive the return URL from the live document location, never from the build-time asset base
|
||||||
|
// (import.meta.env.BASE_URL): the app is mounted at {web.base_path}/app/ in production and at /
|
||||||
|
// under `npm run dev`, so window.location is the only reliable source.
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
currentUrl.hash = router.currentRoute.value.fullPath;
|
currentUrl.hash = router.currentRoute.value.fullPath;
|
||||||
let currentUri = currentUrl.toString();
|
let currentUri = currentUrl.toString();
|
||||||
let redirectUrl = `${WGPORTAL_BACKEND_BASE_URL}${provider.ProviderUrl}`;
|
let redirectUrl = `${WGPORTAL_BACKEND_BASE_URL}${provider.ProviderUrl}`;
|
||||||
|
|||||||
@@ -208,6 +208,8 @@ func (e AuthEndpoint) handleOauthInitiateGet() http.HandlerFunc {
|
|||||||
|
|
||||||
if returnTo != "" {
|
if returnTo != "" {
|
||||||
if !e.isValidReturnUrl(returnTo) {
|
if !e.isValidReturnUrl(returnTo) {
|
||||||
|
slog.Debug("rejected invalid oauth return URL",
|
||||||
|
"provider", provider, "returnTo", returnTo, "expectedPrefix", e.frontendUrl(""))
|
||||||
respond.JSON(w, http.StatusBadRequest,
|
respond.JSON(w, http.StatusBadRequest,
|
||||||
model.Error{Code: http.StatusBadRequest, Message: "invalid return URL"})
|
model.Error{Code: http.StatusBadRequest, Message: "invalid return URL"})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ func newBasePathAuthEndpoint(session Session) AuthEndpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAuthEndpointIsValidReturnUrlRequiresBasePathApp pins the return URL shape that the frontend must
|
||||||
|
// produce. The accepted values are generated by externalLogin() in frontend/src/views/LoginView.vue
|
||||||
|
// from window.location - keep the two in sync when changing either side.
|
||||||
func TestAuthEndpointIsValidReturnUrlRequiresBasePathApp(t *testing.T) {
|
func TestAuthEndpointIsValidReturnUrlRequiresBasePathApp(t *testing.T) {
|
||||||
ep := newBasePathAuthEndpoint(&testSession{})
|
ep := newBasePathAuthEndpoint(&testSession{})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user