Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Daniel Han
b516c12409 fix(studio): don't double-quote the reset-password hint for spaced paths
Addresses review feedback on #5971. _reset_password_command() already
shell-quotes the launcher path on POSIX (shlex.quote), so wrapping the result in
another pair of single quotes in the error string produced a mangled hint for
installs / home dirs containing spaces, e.g.

  Run ''/tmp/Unsloth Studio/.../unsloth' studio reset-password' in your terminal

which a shell mis-parses. Drop the outer quotes and put the command at the end of
the message so it is unambiguous and copy-pasteable in every case:

  Incorrect password. To reset it, run this in your terminal: <cmd>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 06:04:00 -07:00
Daniel Han
98e0a8161f fix(studio): make reset-password hint work without PATH (all platforms)
Follow-up to the auth-form fix. The hint relied on the `unsloth` launcher being
on PATH. That holds in a fresh terminal (the installer adds the launcher dir to
PATH), but the command still fails when:
  - a terminal was opened before install (stale PATH),
  - ~/.local/bin is not on PATH (the default on macOS), or
  - a non-interactive / different shell rc was used.

Have the backend emit the ABSOLUTE path to this install's own `unsloth`
launcher (a sibling of sys.executable) so the command works regardless of PATH
or current directory on Windows, macOS, and Linux. POSIX paths are shell-quoted
(handles spaces). On Windows the bare absolute path is used only when it has no
spaces (works in both cmd and PowerShell); otherwise we fall back to the PATH
form to avoid cmd-vs-PowerShell quoting differences. Falls back to
`unsloth studio reset-password` if the launcher can't be located.

Verified the emitted command runs on Windows (Scripts\unsloth.exe) and Linux
(bin/unsloth) -- both resolve `unsloth studio reset-password`. The CLI command
name is unchanged, so CI smoke steps that call `unsloth studio reset-password`
directly are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 05:28:46 -07:00
Daniel Han
c8be6f6af7 fix(studio): show working reset-password command on Windows login error
The backend's "incorrect password" error already returns the correct,
PATH-based command ("unsloth studio reset-password"), which the installer puts
on PATH on every platform. The auth form rewrote it on Windows into a relative
path:

    .\unsloth_studio\Scripts\unsloth.exe studio reset-password

That only resolves when the terminal happens to be inside the Studio home dir
(~\.unsloth\studio). From any normal working directory (e.g. C:\Users\<you>) it
fails with CommandNotFoundException, so users could not follow the hint to
recover a forgotten password.

Remove the Windows-only rewrite (and its now-unused usePlatformStore import) and
display the backend's command as-is. Frontend typecheck passes (the one
remaining tsc error, @tauri-apps/plugin-window-state in provider.tsx, is a
pre-existing desktop-only module-resolution issue unrelated to this change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 05:21:30 -07:00
2 changed files with 41 additions and 10 deletions

View file

@ -9,6 +9,8 @@ from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
import ipaddress
import os
import shlex
import sys
import threading
import time
from collections import deque
@ -38,6 +40,36 @@ from auth.authentication import (
router = APIRouter()
def _reset_password_command() -> str:
"""Shell command shown in the 'incorrect password' hint.
Prefer the ABSOLUTE path to this install's ``unsloth`` launcher (a sibling
of the running interpreter) so the hint works even when the launcher's
directory is not on PATH -- e.g. a terminal opened before install, a stale
Windows PATH, or ``~/.local/bin`` not on PATH (the default on macOS) -- and
regardless of the current working directory.
On POSIX the path is shell-quoted so spaces are handled. On Windows we only
use the bare absolute path when it has no spaces, because a quoted path needs
different syntax in cmd (``"..."``) vs PowerShell (``& "..."``); when it has
a space we fall back to the PATH-based form to stay unambiguous across
shells. If the launcher can't be located we fall back to the PATH form too.
"""
try:
bin_dir = os.path.dirname(os.path.abspath(sys.executable))
if os.name == "nt":
exe = os.path.join(bin_dir, "unsloth.exe")
if os.path.isfile(exe) and " " not in exe:
return f"{exe} studio reset-password"
else:
exe = os.path.join(bin_dir, "unsloth")
if os.path.isfile(exe):
return f"{shlex.quote(exe)} studio reset-password"
except Exception:
pass
return "unsloth studio reset-password"
# Per-(ip, username) bucket + per-IP aggregate. Account bucket stops one user's
# typos from blocking others; the aggregate stops username-rotation spray.
# Single-process only -- multi-worker deployments need a shared store.
@ -228,7 +260,7 @@ async def login(payload: AuthLoginRequest, request: Request) -> Token:
_record_login_failure(unknown_key)
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Incorrect password. Run 'unsloth studio reset-password' in your terminal to reset it.",
detail = f"Incorrect password. To reset it, run this in your terminal: {_reset_password_command()}",
)
salt, pwd_hash, _jwt_secret, must_change_password = record
@ -236,7 +268,7 @@ async def login(payload: AuthLoginRequest, request: Request) -> Token:
_record_login_failure(key)
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Incorrect password. Run 'unsloth studio reset-password' in your terminal to reset it.",
detail = f"Incorrect password. To reset it, run this in your terminal: {_reset_password_command()}",
)
_clear_login_bucket(key)

View file

@ -10,7 +10,6 @@ import { Eye, EyeOff } from "lucide-react";
import { useEffect, useState } from "react";
import type { ReactElement } from "react";
import type { SyntheticEvent } from "react";
import { usePlatformStore } from "@/config/env";
import { refreshSession } from "../api";
// Bootstrap credentials injected into index.html by the backend
@ -294,13 +293,13 @@ export function AuthForm({ mode }: AuthFormProps): ReactElement | null {
storeAuthTokens(token.access_token, token.refresh_token);
navigate({ to: getPostAuthRoute() });
} catch (err: unknown) {
let msg = err instanceof Error ? err.message : "Auth failed.";
if (msg.includes("unsloth studio reset-password") && usePlatformStore.getState().deviceType === "windows") {
msg = msg.replace(
"unsloth studio reset-password",
".\\unsloth_studio\\Scripts\\unsloth.exe studio reset-password",
);
}
// The backend already returns the correct, PATH-based command
// ("unsloth studio reset-password"), which the installer puts on PATH on
// every platform. Do NOT rewrite it to a relative Windows path like
// ".\unsloth_studio\Scripts\unsloth.exe ..." -- that only resolves when the
// terminal happens to be inside the Studio home dir, so it fails with
// CommandNotFoundException everywhere else. Show the backend message as-is.
const msg = err instanceof Error ? err.message : "Auth failed.";
setError(msg);
} finally {
setLoading(false);