fix(auth): восстанавливать раздел и фильтры после входа Portal
This commit is contained in:
1 parent
199910eceb
commit
46de87e213
15 files changed
+275
-17
No files matched your search
@@ -0,0 +1,9 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {safeAuthReturnPath,permittedAuthReturnPath} from './auth-return-path'
|
||||
for(const input of [null,undefined,'','https://outside.invalid','//outside.invalid','/\\outside.invalid','/auth/callback?next=/servers','/api/servers','/access-denied','/a/../auth/callback','/%2f%2foutside','/%5coutside','/%61uth/callback','/%73ettings','/servers\n','/servers?'+ 'a'.repeat(2048)])assert.equal(safeAuthReturnPath(input),null,String(input))
|
||||
assert.equal(safeAuthReturnPath('/servers?server=42&tab=details#private-token'),'/servers?server=42&tab=details')
|
||||
assert.equal(safeAuthReturnPath('/traffic?period=24h&name=%D0%A2%D0%B5%D1%81%D1%82'),'/traffic?period=24h&name=%D0%A2%D0%B5%D1%81%D1%82')
|
||||
assert.equal(permittedAuthReturnPath('/servers?server=42',p=>p==='/servers','/dashboard'),'/servers?server=42')
|
||||
assert.equal(permittedAuthReturnPath('/settings',p=>p==='/servers','/servers'),'/servers')
|
||||
assert.equal(permittedAuthReturnPath('//outside.invalid',()=>true,'/servers'),'/servers')
|
||||
console.log('auth return path: unsafe targets, normalized auth loops, query preservation and permission fallback passed')
|
||||
@@ -0,0 +1,22 @@
|
||||
/** Accept an application path, never an external URL or another auth handoff. */
|
||||
export function safeAuthReturnPath(value: string | null | undefined): string | null {
|
||||
if (!value || value.length > 2048 || !value.startsWith("/") || value.startsWith("//")) return null
|
||||
if (/[\\\u0000-\u0020\u007f]/.test(value)) return null
|
||||
try {
|
||||
const url = new URL(value, "https://mmapp.invalid")
|
||||
if (url.origin !== "https://mmapp.invalid" || /%/.test(url.pathname)) return null
|
||||
if (/^\/(auth|api|access-denied)(\/|$)/.test(url.pathname)) return null
|
||||
return url.pathname + url.search
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function permittedAuthReturnPath(
|
||||
value: string | null | undefined,
|
||||
allowed: (pathname: string) => boolean,
|
||||
fallback: string,
|
||||
): string {
|
||||
const path = safeAuthReturnPath(value)
|
||||
return path && allowed(new URL(path, "https://mmapp.invalid").pathname) ? path : fallback
|
||||
}
|
||||
+6
-2
@@ -1,4 +1,5 @@
|
||||
/** Portal JWT storage + claims helpers for MikrotikManager. */
|
||||
import { safeAuthReturnPath } from "./auth-return-path"
|
||||
|
||||
const TOKEN_KEY = "mmapp_token"
|
||||
const HANDOFF_KEY = "mmapp_auth_401_handoff"
|
||||
@@ -125,9 +126,12 @@ export function redirectToPortalLogin(returnTo?: string): boolean {
|
||||
return false
|
||||
}
|
||||
markPortalHandoff()
|
||||
const callback = returnTo ?? `${window.location.origin}/auth/callback`
|
||||
const callback = new URL(returnTo ?? `${window.location.origin}/auth/callback`)
|
||||
const next = safeAuthReturnPath(callback.searchParams.get("next")) ??
|
||||
safeAuthReturnPath(window.location.pathname + window.location.search)
|
||||
if (next) callback.searchParams.set("next", next)
|
||||
const url = new URL(authPortalUrl())
|
||||
url.searchParams.set("return_to", callback)
|
||||
url.searchParams.set("return_to", callback.toString())
|
||||
window.location.assign(url.toString())
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user