unsloth/studio/frontend/src/features/auth/session.ts
Roland Tannous 8347bcecdc fix: store provider API keys as plaintext in localStorage
Drop AES-256-GCM at-rest encryption for provider API keys. The
session-password-derived encryption broke on auto-login via refresh
token (password never captured), causing keys to silently vanish.
API keys are still RSA-encrypted in transit via node-forge. At-rest
encryption in localStorage added no real security since the
decryption key also had to live client-side.

Removes crypto-storage.ts, session password plumbing, and
reEncryptAllKeys.
2026-04-08 11:05:42 +04:00

84 lines
2.7 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-only
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
import { usePlatformStore } from "@/config/env";
export const AUTH_TOKEN_KEY = "unsloth_auth_token";
export const AUTH_REFRESH_TOKEN_KEY = "unsloth_auth_refresh_token";
export const ONBOARDING_DONE_KEY = "unsloth_onboarding_done";
export const AUTH_MUST_CHANGE_PASSWORD_KEY = "unsloth_auth_must_change_password";
type PostAuthRoute = "/onboarding" | "/studio" | "/change-password" | "/chat";
function canUseStorage(): boolean {
return typeof window !== "undefined";
}
export function hasAuthToken(): boolean {
if (!canUseStorage()) return false;
return Boolean(localStorage.getItem(AUTH_TOKEN_KEY));
}
export function hasRefreshToken(): boolean {
if (!canUseStorage()) return false;
return Boolean(localStorage.getItem(AUTH_REFRESH_TOKEN_KEY));
}
export function getAuthToken(): string | null {
if (!canUseStorage()) return null;
return localStorage.getItem(AUTH_TOKEN_KEY);
}
export function getRefreshToken(): string | null {
if (!canUseStorage()) return null;
return localStorage.getItem(AUTH_REFRESH_TOKEN_KEY);
}
export function storeAuthTokens(
accessToken: string,
refreshToken: string,
mustChangePassword = false,
): void {
if (!canUseStorage()) return;
localStorage.setItem(AUTH_TOKEN_KEY, accessToken);
localStorage.setItem(AUTH_REFRESH_TOKEN_KEY, refreshToken);
localStorage.setItem(AUTH_MUST_CHANGE_PASSWORD_KEY, String(mustChangePassword));
}
export function clearAuthTokens(): void {
if (!canUseStorage()) return;
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(AUTH_REFRESH_TOKEN_KEY);
localStorage.removeItem(AUTH_MUST_CHANGE_PASSWORD_KEY);
}
export function mustChangePassword(): boolean {
if (!canUseStorage()) return false;
return localStorage.getItem(AUTH_MUST_CHANGE_PASSWORD_KEY) === "true";
}
export function setMustChangePassword(required: boolean): void {
if (!canUseStorage()) return;
localStorage.setItem(AUTH_MUST_CHANGE_PASSWORD_KEY, String(required));
}
export function isOnboardingDone(): boolean {
if (!canUseStorage()) return false;
return localStorage.getItem(ONBOARDING_DONE_KEY) === "true";
}
export function markOnboardingDone(): void {
if (!canUseStorage()) return;
localStorage.setItem(ONBOARDING_DONE_KEY, "true");
}
export function resetOnboardingDone(): void {
if (!canUseStorage()) return;
localStorage.removeItem(ONBOARDING_DONE_KEY);
}
export function getPostAuthRoute(): PostAuthRoute {
if (mustChangePassword()) return "/change-password";
if (usePlatformStore.getState().isChatOnly()) return "/chat";
return isOnboardingDone() ? "/studio" : "/onboarding";
}