Merge pull request #1170 from lcjqyml/feat-multi-user
Feat multi user support.
This commit is contained in:
@@ -157,11 +157,10 @@ QDRANT_URL=http://localhost:16333
|
||||
### Redis
|
||||
REDIS_URI=redis://localhost:6379
|
||||
|
||||
### For JWTt Auth
|
||||
AUTH_USERNAME=admin # login name
|
||||
AUTH_PASSWORD=admin123 # password
|
||||
TOKEN_SECRET=your-key-for-LightRAG-API-Server # JWT key
|
||||
TOKEN_EXPIRE_HOURS=4 # expire duration
|
||||
### For JWT Auth
|
||||
AUTH_ACCOUNTS='admin:admin123,user1:pass456' # username:password,username:password
|
||||
TOKEN_SECRET=Your-Key-For-LightRAG-API-Server # JWT key
|
||||
TOKEN_EXPIRE_HOURS=4 # expire duration
|
||||
|
||||
### API-Key to access LightRAG Server API
|
||||
# LIGHTRAG_API_KEY=your-secure-api-key-here
|
||||
|
@@ -20,9 +20,14 @@ class AuthHandler:
|
||||
self.secret = os.getenv("TOKEN_SECRET", "4f85ds4f56dsf46")
|
||||
self.algorithm = "HS256"
|
||||
self.expire_hours = int(os.getenv("TOKEN_EXPIRE_HOURS", 4))
|
||||
self.guest_expire_hours = int(
|
||||
os.getenv("GUEST_TOKEN_EXPIRE_HOURS", 2)
|
||||
) # Guest token default expiration time
|
||||
self.guest_expire_hours = int(os.getenv("GUEST_TOKEN_EXPIRE_HOURS", 2))
|
||||
|
||||
self.accounts = {}
|
||||
auth_accounts = os.getenv("AUTH_ACCOUNTS")
|
||||
if auth_accounts:
|
||||
for account in auth_accounts.split(","):
|
||||
username, password = account.split(":", 1)
|
||||
self.accounts[username] = password
|
||||
|
||||
def create_token(
|
||||
self,
|
||||
|
@@ -362,10 +362,8 @@ def create_app(args):
|
||||
@app.get("/auth-status")
|
||||
async def get_auth_status():
|
||||
"""Get authentication status and guest token if auth is not configured"""
|
||||
username = os.getenv("AUTH_USERNAME")
|
||||
password = os.getenv("AUTH_PASSWORD")
|
||||
|
||||
if not (username and password):
|
||||
if not auth_handler.accounts:
|
||||
# Authentication not configured, return guest token
|
||||
guest_token = auth_handler.create_token(
|
||||
username="guest", role="guest", metadata={"auth_mode": "disabled"}
|
||||
@@ -389,10 +387,7 @@ def create_app(args):
|
||||
|
||||
@app.post("/login")
|
||||
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
username = os.getenv("AUTH_USERNAME")
|
||||
password = os.getenv("AUTH_PASSWORD")
|
||||
|
||||
if not (username and password):
|
||||
if not auth_handler.accounts:
|
||||
# Authentication not configured, return guest token
|
||||
guest_token = auth_handler.create_token(
|
||||
username="guest", role="guest", metadata={"auth_mode": "disabled"}
|
||||
@@ -405,8 +400,8 @@ def create_app(args):
|
||||
"core_version": core_version,
|
||||
"api_version": __api_version__,
|
||||
}
|
||||
|
||||
if form_data.username != username or form_data.password != password:
|
||||
username = form_data.username
|
||||
if auth_handler.accounts.get(username) != form_data.password:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect credentials"
|
||||
)
|
||||
|
@@ -38,9 +38,7 @@ for path in whitelist_paths:
|
||||
whitelist_patterns.append((path, False)) # (exact_path, is_prefix_match)
|
||||
|
||||
# Global authentication configuration
|
||||
auth_username = os.getenv("AUTH_USERNAME")
|
||||
auth_password = os.getenv("AUTH_PASSWORD")
|
||||
auth_configured = bool(auth_username and auth_password)
|
||||
auth_configured = bool(auth_handler.accounts)
|
||||
|
||||
|
||||
class OllamaServerInfos:
|
||||
|
1
lightrag/api/webui/assets/index-BwFyYQzx.css
generated
Normal file
1
lightrag/api/webui/assets/index-BwFyYQzx.css
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/index-CJhG62dt.css
generated
1
lightrag/api/webui/assets/index-CJhG62dt.css
generated
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
lightrag/api/webui/index.html
generated
4
lightrag/api/webui/index.html
generated
@@ -8,8 +8,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lightrag</title>
|
||||
<script type="module" crossorigin src="/webui/assets/index-DUmKHl1m.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-CJhG62dt.css">
|
||||
<script type="module" crossorigin src="/webui/assets/index-DJ53id6i.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-BwFyYQzx.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@@ -55,7 +55,7 @@ function TabsNavigation() {
|
||||
|
||||
export default function SiteHeader() {
|
||||
const { t } = useTranslation()
|
||||
const { isGuestMode, coreVersion, apiVersion } = useAuthStore()
|
||||
const { isGuestMode, coreVersion, apiVersion, username } = useAuthStore()
|
||||
|
||||
const versionDisplay = (coreVersion && apiVersion)
|
||||
? `${coreVersion}/${apiVersion}`
|
||||
@@ -98,7 +98,13 @@ export default function SiteHeader() {
|
||||
</Button>
|
||||
<AppSettings />
|
||||
{!isGuestMode && (
|
||||
<Button variant="ghost" size="icon" side="bottom" tooltip={t('header.logout')} onClick={handleLogout}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
side="bottom"
|
||||
tooltip={`${t('header.logout')} (${username})`}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOutIcon className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
)}
|
||||
|
@@ -21,6 +21,8 @@ interface AuthState {
|
||||
isGuestMode: boolean; // Add guest mode flag
|
||||
coreVersion: string | null;
|
||||
apiVersion: string | null;
|
||||
username: string | null; // login username
|
||||
|
||||
login: (token: string, isGuest?: boolean, coreVersion?: string | null, apiVersion?: string | null) => void;
|
||||
logout: () => void;
|
||||
setVersion: (coreVersion: string | null, apiVersion: string | null) => void;
|
||||
@@ -76,36 +78,42 @@ const useBackendState = createSelectors(useBackendStateStoreBase)
|
||||
|
||||
export { useBackendState }
|
||||
|
||||
// Helper function to check if token is a guest token
|
||||
const isGuestToken = (token: string): boolean => {
|
||||
const parseTokenPayload = (token: string): { sub?: string; role?: string } => {
|
||||
try {
|
||||
// JWT tokens are in the format: header.payload.signature
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) return false;
|
||||
|
||||
// Decode the payload (second part)
|
||||
if (parts.length !== 3) return {};
|
||||
const payload = JSON.parse(atob(parts[1]));
|
||||
|
||||
// Check if the token has a role field with value "guest"
|
||||
return payload.role === 'guest';
|
||||
return payload;
|
||||
} catch (e) {
|
||||
console.error('Error parsing token:', e);
|
||||
return false;
|
||||
console.error('Error parsing token payload:', e);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize auth state from localStorage
|
||||
const initAuthState = (): { isAuthenticated: boolean; isGuestMode: boolean; coreVersion: string | null; apiVersion: string | null } => {
|
||||
const getUsernameFromToken = (token: string): string | null => {
|
||||
const payload = parseTokenPayload(token);
|
||||
return payload.sub || null;
|
||||
};
|
||||
|
||||
const isGuestToken = (token: string): boolean => {
|
||||
const payload = parseTokenPayload(token);
|
||||
return payload.role === 'guest';
|
||||
};
|
||||
|
||||
const initAuthState = (): { isAuthenticated: boolean; isGuestMode: boolean; coreVersion: string | null; apiVersion: string | null; username: string | null } => {
|
||||
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
||||
const coreVersion = localStorage.getItem('LIGHTRAG-CORE-VERSION');
|
||||
const apiVersion = localStorage.getItem('LIGHTRAG-API-VERSION');
|
||||
const username = token ? getUsernameFromToken(token) : null;
|
||||
|
||||
if (!token) {
|
||||
return {
|
||||
isAuthenticated: false,
|
||||
isGuestMode: false,
|
||||
coreVersion: coreVersion,
|
||||
apiVersion: apiVersion
|
||||
apiVersion: apiVersion,
|
||||
username: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -113,7 +121,8 @@ const initAuthState = (): { isAuthenticated: boolean; isGuestMode: boolean; core
|
||||
isAuthenticated: true,
|
||||
isGuestMode: isGuestToken(token),
|
||||
coreVersion: coreVersion,
|
||||
apiVersion: apiVersion
|
||||
apiVersion: apiVersion,
|
||||
username: username,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -126,6 +135,7 @@ export const useAuthStore = create<AuthState>(set => {
|
||||
isGuestMode: initialState.isGuestMode,
|
||||
coreVersion: initialState.coreVersion,
|
||||
apiVersion: initialState.apiVersion,
|
||||
username: initialState.username,
|
||||
|
||||
login: (token, isGuest = false, coreVersion = null, apiVersion = null) => {
|
||||
localStorage.setItem('LIGHTRAG-API-TOKEN', token);
|
||||
@@ -137,11 +147,13 @@ export const useAuthStore = create<AuthState>(set => {
|
||||
localStorage.setItem('LIGHTRAG-API-VERSION', apiVersion);
|
||||
}
|
||||
|
||||
const username = getUsernameFromToken(token);
|
||||
set({
|
||||
isAuthenticated: true,
|
||||
isGuestMode: isGuest,
|
||||
username: username,
|
||||
coreVersion: coreVersion,
|
||||
apiVersion: apiVersion
|
||||
apiVersion: apiVersion,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -154,8 +166,9 @@ export const useAuthStore = create<AuthState>(set => {
|
||||
set({
|
||||
isAuthenticated: false,
|
||||
isGuestMode: false,
|
||||
username: null,
|
||||
coreVersion: coreVersion,
|
||||
apiVersion: apiVersion
|
||||
apiVersion: apiVersion,
|
||||
});
|
||||
},
|
||||
|
||||
|
Reference in New Issue
Block a user