mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
* refactor(auth): add sanitizeNextUrl helper in @multica/core/auth Extracts a reusable helper that returns a post-login redirect URL only when it's a safe single-slash relative path, and null otherwise. Rejects absolute URLs, protocol-relative URLs, backslashes, and control characters so call sites can safely pass the result to router.push(). Keeping the rule in a single helper (with direct unit tests) avoids each consumer re-implementing the validation and drifting. * fix(auth): validate next= redirect target to prevent open redirect Closes #1116 Next.js router.push accepts absolute URLs, so a crafted `/login?next=https://evil.example` would send the user off-origin after a successful login. The Google OAuth callback has the same vector via the `state=next:<url>` payload. Sanitize both entry points through `sanitizeNextUrl` from `@multica/core/auth` so only safe single-slash relative paths survive; null results fall through to the existing workspace-list-based default without any hard-coded path. --------- Co-authored-by: JunghwanNA <70629228+shaun0927@users.noreply.github.com>
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
export { createAuthStore } from "./store";
|
|
export type { AuthStoreOptions, AuthState } from "./store";
|
|
export { sanitizeNextUrl } from "./utils";
|
|
|
|
import type { createAuthStore as CreateAuthStoreFn } from "./store";
|
|
|
|
type AuthStoreInstance = ReturnType<typeof CreateAuthStoreFn>;
|
|
|
|
/** Module-level singleton — set once at app boot via `registerAuthStore()`. */
|
|
let _store: AuthStoreInstance | null = null;
|
|
|
|
/**
|
|
* Register the auth store instance created by the app.
|
|
* Must be called at boot before any component renders.
|
|
*/
|
|
export function registerAuthStore(store: AuthStoreInstance) {
|
|
_store = store;
|
|
}
|
|
|
|
/**
|
|
* Singleton accessor — a Zustand hook backed by the registered instance.
|
|
* Supports `useAuthStore(selector)` and `useAuthStore.getState()`.
|
|
*/
|
|
export const useAuthStore: AuthStoreInstance = new Proxy(
|
|
(() => {}) as unknown as AuthStoreInstance,
|
|
{
|
|
apply(_target, _thisArg, args) {
|
|
if (!_store)
|
|
throw new Error(
|
|
"Auth store not initialised — call registerAuthStore() first",
|
|
);
|
|
return (_store as unknown as (...a: unknown[]) => unknown)(...args);
|
|
},
|
|
get(_target, prop) {
|
|
// Allow property inspection (HMR/React Refresh) before registration
|
|
if (!_store) return undefined;
|
|
return Reflect.get(_store, prop);
|
|
},
|
|
},
|
|
);
|