Fix sign-in links and legacy JWT after Periscope rebrand.

/login 404ed from marketing CTAs; redirect to /sign-in and accept pinscope-local tokens plus dual-host CORS so login works on both hostnames.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-13 20:12:53 +02:00
co-authored by Cursor
parent 8d2b85600f
commit dc364f5926
8 changed files with 51 additions and 23 deletions
+4 -1
View File
@@ -32,14 +32,17 @@ def decode_token(token: str) -> dict[str, Any] | None:
secret = settings.auth_jwt_secret secret = settings.auth_jwt_secret
if not secret: if not secret:
return None return None
# Accept pre-rebrand issuer so existing sessions keep working.
for issuer in ("periscope-local", "pinscope-local"):
try: try:
return jwt.decode( return jwt.decode(
token, token,
secret, secret,
algorithms=[ALGORITHM], algorithms=[ALGORITHM],
issuer="periscope-local", issuer=issuer,
options={"verify_aud": False}, options={"verify_aud": False},
leeway=10, leeway=10,
) )
except jwt.PyJWTError: except jwt.PyJWTError:
continue
return None return None
+8
View File
@@ -2,6 +2,14 @@
What's new in Periscope. What's new in Periscope.
## 2.29.1 — 2026-09-13 — Fix sign-in /login 404 after rebrand
Landing CTAs pointed at `/login`, which did not exist (page is `/sign-in`). Added redirects, accepted legacy JWT issuer `pinscope-local`, and CORS for both Periscope and Pinscope hosts.
- [Fixed] `/login``/sign-in`, `/register``/sign-up`; marketing links updated.
- [Fixed] JWT decode accepts `pinscope-local` issuer from pre-rebrand sessions.
- [Fixed] Deploy script CORS includes both public hostnames.
## 2.29.0 — 2026-09-13 — Rebrand to Periscope ## 2.29.0 — 2026-09-13 — Rebrand to Periscope
Product name, package (`periscopex`), Docker containers, deploy script, and default site URL are now **Periscope** (`https://periscope.michelebigi.it`). Deterministic finding IDs use the `PE-*` prefix. Existing project.json `pinscope_version` and browser storage keys are still read. Product name, package (`periscopex`), Docker containers, deploy script, and default site URL are now **Periscope** (`https://periscope.michelebigi.it`). Deterministic finding IDs use the `PE-*` prefix. Existing project.json `pinscope_version` and browser storage keys are still read.
+6
View File
@@ -27,6 +27,12 @@ const nextConfig: NextConfig = {
canvas: { browser: "" }, canvas: { browser: "" },
}, },
}, },
async redirects() {
return [
{ source: "/login", destination: "/sign-in", permanent: false },
{ source: "/register", destination: "/sign-up", permanent: false },
];
},
async headers() { async headers() {
return [ return [
{ {
+2 -2
View File
@@ -44,12 +44,12 @@ export function Nav() {
</Link> </Link>
) : ( ) : (
<> <>
<Link href="/login"> <Link href="/sign-in">
<Button variant="ghost" size="sm"> <Button variant="ghost" size="sm">
Sign in Sign in
</Button> </Button>
</Link> </Link>
<Link href="/login"> <Link href="/sign-up">
<Button <Button
size="sm" size="sm"
className="bg-blue-600 hover:bg-blue-500 text-white border-0" className="bg-blue-600 hover:bg-blue-500 text-white border-0"
+4 -4
View File
@@ -22,12 +22,12 @@ export function NavAuthCluster() {
} }
return ( return (
<> <>
<Link href="/login"> <Link href="/sign-in">
<Button variant="ghost" size="sm"> <Button variant="ghost" size="sm">
Sign in Sign in
</Button> </Button>
</Link> </Link>
<Link href="/login"> <Link href="/sign-up">
<Button <Button
size="sm" size="sm"
className="bg-blue-600 hover:bg-blue-500 text-white border-0" className="bg-blue-600 hover:bg-blue-500 text-white border-0"
@@ -41,7 +41,7 @@ export function NavAuthCluster() {
export function PrimaryCta() { export function PrimaryCta() {
const { isSignedIn } = useOptionalAuth(); const { isSignedIn } = useOptionalAuth();
const href = isSignedIn ? "/dashboard" : "/login"; const href = isSignedIn ? "/dashboard" : "/sign-up";
const label = isSignedIn ? "Go to Dashboard" : "Start for free"; const label = isSignedIn ? "Go to Dashboard" : "Start for free";
return ( return (
<Link href={href}> <Link href={href}>
@@ -59,7 +59,7 @@ export function PrimaryCta() {
export function PricingCta() { export function PricingCta() {
const { isSignedIn } = useOptionalAuth(); const { isSignedIn } = useOptionalAuth();
return ( return (
<Link href={isSignedIn ? "/billing" : "/login"}> <Link href={isSignedIn ? "/billing" : "/sign-up"}>
<Button <Button
size="sm" size="sm"
className="bg-blue-600 hover:bg-blue-500 text-white border-0 gap-1.5" className="bg-blue-600 hover:bg-blue-500 text-white border-0 gap-1.5"
@@ -29,7 +29,14 @@ export default function SignInPage() {
}); });
const data = await res.json().catch(() => ({})); const data = await res.json().catch(() => ({}));
if (!res.ok) { if (!res.ok) {
throw new Error(data.detail || "Sign in failed"); const detail = data.detail;
const msg =
typeof detail === "string"
? detail
: Array.isArray(detail)
? detail.map((d: { msg?: string }) => d.msg || JSON.stringify(d)).join("; ")
: "Sign in failed";
throw new Error(msg);
} }
storeAuthToken(data.token); storeAuthToken(data.token);
router.replace("/"); router.replace("/");
@@ -35,9 +35,13 @@ export default function SignUpPage() {
const data = await res.json().catch(() => ({})); const data = await res.json().catch(() => ({}));
if (!res.ok) { if (!res.ok) {
const detail = data.detail; const detail = data.detail;
throw new Error( const msg =
typeof detail === "string" ? detail : "Could not create account", typeof detail === "string"
); ? detail
: Array.isArray(detail)
? detail.map((d: { msg?: string }) => d.msg || JSON.stringify(d)).join("; ")
: "Could not create account";
throw new Error(msg);
} }
storeAuthToken(data.token); storeAuthToken(data.token);
router.replace("/"); router.replace("/");
+2 -2
View File
@@ -115,8 +115,8 @@ fi
log "Ensuring public URL in .env ($SITE)" log "Ensuring public URL in .env ($SITE)"
upsert_env NEXT_PUBLIC_API_URL "$SITE" .env upsert_env NEXT_PUBLIC_API_URL "$SITE" .env
# JSON list — keep it a single line so docker compose / pydantic-settings parse it. # Allow both Periscope and legacy Pinscope host during transition.
upsert_env CORS_ORIGINS "[\"$SITE\"]" .env upsert_env CORS_ORIGINS "[\"$SITE\",\"https://pinscope.michelebigi.it\"]" .env
KEY="$(read_env DEEPSEEK_API_KEY .env || true)" KEY="$(read_env DEEPSEEK_API_KEY .env || true)"
if [[ -z "$KEY" || "$KEY" == "sk-..." ]]; then if [[ -z "$KEY" || "$KEY" == "sk-..." ]]; then