first commit

This commit is contained in:
domrichardson
2026-03-24 16:03:04 +00:00
commit df40cc57e1
80 changed files with 16766 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "../stores/authStore";
import { useSettingsStore } from "../stores/settingsStore";
import LoginPage from "../pages/Login.vue";
import RegisterPage from "../pages/Register.vue";
const decodeBase64UrlUTF8 = (value) => {
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
const padding = normalized.length % 4;
const padded = padding === 0 ? normalized : `${normalized}${"=".repeat(4 - padding)}`;
const binary = atob(padded);
const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
return new TextDecoder().decode(bytes);
};
const restoreOAuthSessionFromQuery = (query, authStore) => {
// Merge router query with URLSearchParams for full coverage
const params = new URLSearchParams(window.location.search);
const accessToken = query.access_token || query.accessToken || query.token || params.get("access_token") || params.get("accessToken") || params.get("token");
if (!accessToken) {
return false;
}
try {
const plainUserJSON = query.user_json || params.get("user_json");
const encodedUser = query.user || params.get("user");
const user = plainUserJSON ? JSON.parse(plainUserJSON) : encodedUser ? JSON.parse(decodeBase64UrlUTF8(encodedUser)) : null;
if (!user) {
return false;
}
authStore.setSession({ access_token: accessToken, user });
return true;
} catch {
return false;
}
};
const routes = [
{
path: "/login",
name: "Login",
component: LoginPage,
},
{
path: "/register",
name: "Register",
component: RegisterPage,
},
{
path: "/",
name: "Home",
component: () => import("../pages/Home.vue"),
meta: { requiresAuth: true },
},
{
path: "/admin",
name: "Admin",
component: () => import("../pages/Admin.vue"),
meta: { requiresAuth: true, requiresAdminPermission: true },
},
{
path: "/s/:spaceId",
name: "PublicSpace",
component: () => import("../pages/PublicSpace.vue"),
},
{
path: "/s/:spaceId/n/:noteId",
name: "PublicNote",
component: () => import("../pages/PublicSpace.vue"),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
const settingsStore = useSettingsStore();
// Only attempt OAuth callback restoration if actual OAuth query params are present
const params = new URLSearchParams(window.location.search);
const hasOAuthParams = to.query.access_token || to.query.accessToken || to.query.token || params.get("access_token") || params.get("accessToken") || params.get("token");
if (to.path === "/login") {
if (hasOAuthParams) {
const restored = restoreOAuthSessionFromQuery(to.query, authStore);
if (restored) {
next({ path: "/", replace: true });
return;
}
}
// Allow login page to be viewed regardless of auth state if no OAuth callback
if (!hasOAuthParams) {
next();
return;
}
}
if (to.path === "/register") {
await settingsStore.loadFeatureFlags();
if (!settingsStore.registrationEnabled) {
next({ path: "/login", query: { message: "Registration is currently disabled." } });
return;
}
}
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next("/login");
} else if (to.meta.requiresAdminPermission && !authStore.isAdmin) {
next("/");
} else if ((to.path === "/login" || to.path === "/register") && authStore.isAuthenticated) {
next("/");
} else {
next();
}
});
export default router;