Merge pull request #1170 from lcjqyml/feat-multi-user
Browse files- env.example +4 -5
- lightrag/api/auth.py +8 -3
- lightrag/api/lightrag_server.py +4 -9
- lightrag/api/utils_api.py +1 -3
- lightrag/api/webui/assets/index-BwFyYQzx.css +0 -0
- lightrag/api/webui/assets/index-CJhG62dt.css +0 -0
- lightrag/api/webui/assets/{index-DUmKHl1m.js → index-DJ53id6i.js} +0 -0
- lightrag/api/webui/index.html +0 -0
- lightrag_webui/src/features/SiteHeader.tsx +8 -2
- lightrag_webui/src/stores/state.ts +29 -16
env.example
CHANGED
@@ -157,11 +157,10 @@ QDRANT_URL=http://localhost:16333
|
|
157 |
### Redis
|
158 |
REDIS_URI=redis://localhost:6379
|
159 |
|
160 |
-
### For
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
TOKEN_EXPIRE_HOURS=4 # expire duration
|
165 |
|
166 |
### API-Key to access LightRAG Server API
|
167 |
# LIGHTRAG_API_KEY=your-secure-api-key-here
|
|
|
157 |
### Redis
|
158 |
REDIS_URI=redis://localhost:6379
|
159 |
|
160 |
+
### For JWT Auth
|
161 |
+
AUTH_ACCOUNTS='admin:admin123,user1:pass456' # username:password,username:password
|
162 |
+
TOKEN_SECRET=Your-Key-For-LightRAG-API-Server # JWT key
|
163 |
+
TOKEN_EXPIRE_HOURS=4 # expire duration
|
|
|
164 |
|
165 |
### API-Key to access LightRAG Server API
|
166 |
# LIGHTRAG_API_KEY=your-secure-api-key-here
|
lightrag/api/auth.py
CHANGED
@@ -20,9 +20,14 @@ class AuthHandler:
|
|
20 |
self.secret = os.getenv("TOKEN_SECRET", "4f85ds4f56dsf46")
|
21 |
self.algorithm = "HS256"
|
22 |
self.expire_hours = int(os.getenv("TOKEN_EXPIRE_HOURS", 4))
|
23 |
-
self.guest_expire_hours = int(
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
def create_token(
|
28 |
self,
|
|
|
20 |
self.secret = os.getenv("TOKEN_SECRET", "4f85ds4f56dsf46")
|
21 |
self.algorithm = "HS256"
|
22 |
self.expire_hours = int(os.getenv("TOKEN_EXPIRE_HOURS", 4))
|
23 |
+
self.guest_expire_hours = int(os.getenv("GUEST_TOKEN_EXPIRE_HOURS", 2))
|
24 |
+
|
25 |
+
self.accounts = {}
|
26 |
+
auth_accounts = os.getenv("AUTH_ACCOUNTS")
|
27 |
+
if auth_accounts:
|
28 |
+
for account in auth_accounts.split(","):
|
29 |
+
username, password = account.split(":", 1)
|
30 |
+
self.accounts[username] = password
|
31 |
|
32 |
def create_token(
|
33 |
self,
|
lightrag/api/lightrag_server.py
CHANGED
@@ -362,10 +362,8 @@ def create_app(args):
|
|
362 |
@app.get("/auth-status")
|
363 |
async def get_auth_status():
|
364 |
"""Get authentication status and guest token if auth is not configured"""
|
365 |
-
username = os.getenv("AUTH_USERNAME")
|
366 |
-
password = os.getenv("AUTH_PASSWORD")
|
367 |
|
368 |
-
if not
|
369 |
# Authentication not configured, return guest token
|
370 |
guest_token = auth_handler.create_token(
|
371 |
username="guest", role="guest", metadata={"auth_mode": "disabled"}
|
@@ -389,10 +387,7 @@ def create_app(args):
|
|
389 |
|
390 |
@app.post("/login")
|
391 |
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
392 |
-
|
393 |
-
password = os.getenv("AUTH_PASSWORD")
|
394 |
-
|
395 |
-
if not (username and password):
|
396 |
# Authentication not configured, return guest token
|
397 |
guest_token = auth_handler.create_token(
|
398 |
username="guest", role="guest", metadata={"auth_mode": "disabled"}
|
@@ -405,8 +400,8 @@ def create_app(args):
|
|
405 |
"core_version": core_version,
|
406 |
"api_version": __api_version__,
|
407 |
}
|
408 |
-
|
409 |
-
if
|
410 |
raise HTTPException(
|
411 |
status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect credentials"
|
412 |
)
|
|
|
362 |
@app.get("/auth-status")
|
363 |
async def get_auth_status():
|
364 |
"""Get authentication status and guest token if auth is not configured"""
|
|
|
|
|
365 |
|
366 |
+
if not auth_handler.accounts:
|
367 |
# Authentication not configured, return guest token
|
368 |
guest_token = auth_handler.create_token(
|
369 |
username="guest", role="guest", metadata={"auth_mode": "disabled"}
|
|
|
387 |
|
388 |
@app.post("/login")
|
389 |
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
390 |
+
if not auth_handler.accounts:
|
|
|
|
|
|
|
391 |
# Authentication not configured, return guest token
|
392 |
guest_token = auth_handler.create_token(
|
393 |
username="guest", role="guest", metadata={"auth_mode": "disabled"}
|
|
|
400 |
"core_version": core_version,
|
401 |
"api_version": __api_version__,
|
402 |
}
|
403 |
+
username = form_data.username
|
404 |
+
if auth_handler.accounts.get(username) != form_data.password:
|
405 |
raise HTTPException(
|
406 |
status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect credentials"
|
407 |
)
|
lightrag/api/utils_api.py
CHANGED
@@ -38,9 +38,7 @@ for path in whitelist_paths:
|
|
38 |
whitelist_patterns.append((path, False)) # (exact_path, is_prefix_match)
|
39 |
|
40 |
# Global authentication configuration
|
41 |
-
|
42 |
-
auth_password = os.getenv("AUTH_PASSWORD")
|
43 |
-
auth_configured = bool(auth_username and auth_password)
|
44 |
|
45 |
|
46 |
class OllamaServerInfos:
|
|
|
38 |
whitelist_patterns.append((path, False)) # (exact_path, is_prefix_match)
|
39 |
|
40 |
# Global authentication configuration
|
41 |
+
auth_configured = bool(auth_handler.accounts)
|
|
|
|
|
42 |
|
43 |
|
44 |
class OllamaServerInfos:
|
lightrag/api/webui/assets/index-BwFyYQzx.css
ADDED
Binary file (52.2 kB). View file
|
|
lightrag/api/webui/assets/index-CJhG62dt.css
DELETED
Binary file (52 kB)
|
|
lightrag/api/webui/assets/{index-DUmKHl1m.js → index-DJ53id6i.js}
RENAMED
Binary files a/lightrag/api/webui/assets/index-DUmKHl1m.js and b/lightrag/api/webui/assets/index-DJ53id6i.js differ
|
|
lightrag/api/webui/index.html
CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
|
|
lightrag_webui/src/features/SiteHeader.tsx
CHANGED
@@ -55,7 +55,7 @@ function TabsNavigation() {
|
|
55 |
|
56 |
export default function SiteHeader() {
|
57 |
const { t } = useTranslation()
|
58 |
-
const { isGuestMode, coreVersion, apiVersion } = useAuthStore()
|
59 |
|
60 |
const versionDisplay = (coreVersion && apiVersion)
|
61 |
? `${coreVersion}/${apiVersion}`
|
@@ -98,7 +98,13 @@ export default function SiteHeader() {
|
|
98 |
</Button>
|
99 |
<AppSettings />
|
100 |
{!isGuestMode && (
|
101 |
-
<Button
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
<LogOutIcon className="size-4" aria-hidden="true" />
|
103 |
</Button>
|
104 |
)}
|
|
|
55 |
|
56 |
export default function SiteHeader() {
|
57 |
const { t } = useTranslation()
|
58 |
+
const { isGuestMode, coreVersion, apiVersion, username } = useAuthStore()
|
59 |
|
60 |
const versionDisplay = (coreVersion && apiVersion)
|
61 |
? `${coreVersion}/${apiVersion}`
|
|
|
98 |
</Button>
|
99 |
<AppSettings />
|
100 |
{!isGuestMode && (
|
101 |
+
<Button
|
102 |
+
variant="ghost"
|
103 |
+
size="icon"
|
104 |
+
side="bottom"
|
105 |
+
tooltip={`${t('header.logout')} (${username})`}
|
106 |
+
onClick={handleLogout}
|
107 |
+
>
|
108 |
<LogOutIcon className="size-4" aria-hidden="true" />
|
109 |
</Button>
|
110 |
)}
|
lightrag_webui/src/stores/state.ts
CHANGED
@@ -21,6 +21,8 @@ interface AuthState {
|
|
21 |
isGuestMode: boolean; // Add guest mode flag
|
22 |
coreVersion: string | null;
|
23 |
apiVersion: string | null;
|
|
|
|
|
24 |
login: (token: string, isGuest?: boolean, coreVersion?: string | null, apiVersion?: string | null) => void;
|
25 |
logout: () => void;
|
26 |
setVersion: (coreVersion: string | null, apiVersion: string | null) => void;
|
@@ -76,36 +78,42 @@ const useBackendState = createSelectors(useBackendStateStoreBase)
|
|
76 |
|
77 |
export { useBackendState }
|
78 |
|
79 |
-
|
80 |
-
const isGuestToken = (token: string): boolean => {
|
81 |
try {
|
82 |
// JWT tokens are in the format: header.payload.signature
|
83 |
const parts = token.split('.');
|
84 |
-
if (parts.length !== 3) return
|
85 |
-
|
86 |
-
// Decode the payload (second part)
|
87 |
const payload = JSON.parse(atob(parts[1]));
|
88 |
-
|
89 |
-
// Check if the token has a role field with value "guest"
|
90 |
-
return payload.role === 'guest';
|
91 |
} catch (e) {
|
92 |
-
console.error('Error parsing token:', e);
|
93 |
-
return
|
94 |
}
|
95 |
};
|
96 |
|
97 |
-
|
98 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
100 |
const coreVersion = localStorage.getItem('LIGHTRAG-CORE-VERSION');
|
101 |
const apiVersion = localStorage.getItem('LIGHTRAG-API-VERSION');
|
|
|
102 |
|
103 |
if (!token) {
|
104 |
return {
|
105 |
isAuthenticated: false,
|
106 |
isGuestMode: false,
|
107 |
coreVersion: coreVersion,
|
108 |
-
apiVersion: apiVersion
|
|
|
109 |
};
|
110 |
}
|
111 |
|
@@ -113,7 +121,8 @@ const initAuthState = (): { isAuthenticated: boolean; isGuestMode: boolean; core
|
|
113 |
isAuthenticated: true,
|
114 |
isGuestMode: isGuestToken(token),
|
115 |
coreVersion: coreVersion,
|
116 |
-
apiVersion: apiVersion
|
|
|
117 |
};
|
118 |
};
|
119 |
|
@@ -126,6 +135,7 @@ export const useAuthStore = create<AuthState>(set => {
|
|
126 |
isGuestMode: initialState.isGuestMode,
|
127 |
coreVersion: initialState.coreVersion,
|
128 |
apiVersion: initialState.apiVersion,
|
|
|
129 |
|
130 |
login: (token, isGuest = false, coreVersion = null, apiVersion = null) => {
|
131 |
localStorage.setItem('LIGHTRAG-API-TOKEN', token);
|
@@ -137,11 +147,13 @@ export const useAuthStore = create<AuthState>(set => {
|
|
137 |
localStorage.setItem('LIGHTRAG-API-VERSION', apiVersion);
|
138 |
}
|
139 |
|
|
|
140 |
set({
|
141 |
isAuthenticated: true,
|
142 |
isGuestMode: isGuest,
|
|
|
143 |
coreVersion: coreVersion,
|
144 |
-
apiVersion: apiVersion
|
145 |
});
|
146 |
},
|
147 |
|
@@ -154,8 +166,9 @@ export const useAuthStore = create<AuthState>(set => {
|
|
154 |
set({
|
155 |
isAuthenticated: false,
|
156 |
isGuestMode: false,
|
|
|
157 |
coreVersion: coreVersion,
|
158 |
-
apiVersion: apiVersion
|
159 |
});
|
160 |
},
|
161 |
|
|
|
21 |
isGuestMode: boolean; // Add guest mode flag
|
22 |
coreVersion: string | null;
|
23 |
apiVersion: string | null;
|
24 |
+
username: string | null; // login username
|
25 |
+
|
26 |
login: (token: string, isGuest?: boolean, coreVersion?: string | null, apiVersion?: string | null) => void;
|
27 |
logout: () => void;
|
28 |
setVersion: (coreVersion: string | null, apiVersion: string | null) => void;
|
|
|
78 |
|
79 |
export { useBackendState }
|
80 |
|
81 |
+
const parseTokenPayload = (token: string): { sub?: string; role?: string } => {
|
|
|
82 |
try {
|
83 |
// JWT tokens are in the format: header.payload.signature
|
84 |
const parts = token.split('.');
|
85 |
+
if (parts.length !== 3) return {};
|
|
|
|
|
86 |
const payload = JSON.parse(atob(parts[1]));
|
87 |
+
return payload;
|
|
|
|
|
88 |
} catch (e) {
|
89 |
+
console.error('Error parsing token payload:', e);
|
90 |
+
return {};
|
91 |
}
|
92 |
};
|
93 |
|
94 |
+
const getUsernameFromToken = (token: string): string | null => {
|
95 |
+
const payload = parseTokenPayload(token);
|
96 |
+
return payload.sub || null;
|
97 |
+
};
|
98 |
+
|
99 |
+
const isGuestToken = (token: string): boolean => {
|
100 |
+
const payload = parseTokenPayload(token);
|
101 |
+
return payload.role === 'guest';
|
102 |
+
};
|
103 |
+
|
104 |
+
const initAuthState = (): { isAuthenticated: boolean; isGuestMode: boolean; coreVersion: string | null; apiVersion: string | null; username: string | null } => {
|
105 |
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
106 |
const coreVersion = localStorage.getItem('LIGHTRAG-CORE-VERSION');
|
107 |
const apiVersion = localStorage.getItem('LIGHTRAG-API-VERSION');
|
108 |
+
const username = token ? getUsernameFromToken(token) : null;
|
109 |
|
110 |
if (!token) {
|
111 |
return {
|
112 |
isAuthenticated: false,
|
113 |
isGuestMode: false,
|
114 |
coreVersion: coreVersion,
|
115 |
+
apiVersion: apiVersion,
|
116 |
+
username: null,
|
117 |
};
|
118 |
}
|
119 |
|
|
|
121 |
isAuthenticated: true,
|
122 |
isGuestMode: isGuestToken(token),
|
123 |
coreVersion: coreVersion,
|
124 |
+
apiVersion: apiVersion,
|
125 |
+
username: username,
|
126 |
};
|
127 |
};
|
128 |
|
|
|
135 |
isGuestMode: initialState.isGuestMode,
|
136 |
coreVersion: initialState.coreVersion,
|
137 |
apiVersion: initialState.apiVersion,
|
138 |
+
username: initialState.username,
|
139 |
|
140 |
login: (token, isGuest = false, coreVersion = null, apiVersion = null) => {
|
141 |
localStorage.setItem('LIGHTRAG-API-TOKEN', token);
|
|
|
147 |
localStorage.setItem('LIGHTRAG-API-VERSION', apiVersion);
|
148 |
}
|
149 |
|
150 |
+
const username = getUsernameFromToken(token);
|
151 |
set({
|
152 |
isAuthenticated: true,
|
153 |
isGuestMode: isGuest,
|
154 |
+
username: username,
|
155 |
coreVersion: coreVersion,
|
156 |
+
apiVersion: apiVersion,
|
157 |
});
|
158 |
},
|
159 |
|
|
|
166 |
set({
|
167 |
isAuthenticated: false,
|
168 |
isGuestMode: false,
|
169 |
+
username: null,
|
170 |
coreVersion: coreVersion,
|
171 |
+
apiVersion: apiVersion,
|
172 |
});
|
173 |
},
|
174 |
|