Daniel.y commited on
Commit
70ded13
·
unverified ·
2 Parent(s): 514c7d5 6abba01

Merge pull request #1170 from lcjqyml/feat-multi-user

Browse files
env.example CHANGED
@@ -157,11 +157,10 @@ QDRANT_URL=http://localhost:16333
157
  ### Redis
158
  REDIS_URI=redis://localhost:6379
159
 
160
- ### For JWTt Auth
161
- AUTH_USERNAME=admin # login name
162
- AUTH_PASSWORD=admin123 # password
163
- TOKEN_SECRET=your-key-for-LightRAG-API-Server # JWT key
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
- os.getenv("GUEST_TOKEN_EXPIRE_HOURS", 2)
25
- ) # Guest token default expiration time
 
 
 
 
 
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 (username and password):
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
- username = os.getenv("AUTH_USERNAME")
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 form_data.username != username or form_data.password != password:
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
- auth_username = os.getenv("AUTH_USERNAME")
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 variant="ghost" size="icon" side="bottom" tooltip={t('header.logout')} onClick={handleLogout}>
 
 
 
 
 
 
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
- // Helper function to check if token is a guest token
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 false;
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 false;
94
  }
95
  };
96
 
97
- // Initialize auth state from localStorage
98
- const initAuthState = (): { isAuthenticated: boolean; isGuestMode: boolean; coreVersion: string | null; apiVersion: string | null } => {
 
 
 
 
 
 
 
 
 
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