Compare commits
3 commits
main
...
fix/studio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b516c12409 | ||
|
|
98e0a8161f | ||
|
|
c8be6f6af7 |
2 changed files with 41 additions and 10 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue