auths.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. import re
  2. import uuid
  3. import time
  4. import datetime
  5. from open_webui.apps.webui.models.auths import (
  6. AddUserForm,
  7. ApiKey,
  8. Auths,
  9. Token,
  10. SigninForm,
  11. SigninResponse,
  12. SignupForm,
  13. UpdatePasswordForm,
  14. UpdateProfileForm,
  15. UserResponse,
  16. )
  17. from open_webui.apps.webui.models.users import Users
  18. from open_webui.config import WEBUI_AUTH
  19. from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
  20. from open_webui.env import (
  21. WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
  22. WEBUI_AUTH_TRUSTED_NAME_HEADER,
  23. WEBUI_SESSION_COOKIE_SAME_SITE,
  24. WEBUI_SESSION_COOKIE_SECURE,
  25. )
  26. from fastapi import APIRouter, Depends, HTTPException, Request, status
  27. from fastapi.responses import Response
  28. from pydantic import BaseModel
  29. from open_webui.utils.misc import parse_duration, validate_email_format
  30. from open_webui.utils.utils import (
  31. create_api_key,
  32. create_token,
  33. get_admin_user,
  34. get_verified_user,
  35. get_current_user,
  36. get_password_hash,
  37. )
  38. from open_webui.utils.webhook import post_webhook
  39. from typing import Optional
  40. router = APIRouter()
  41. ############################
  42. # GetSessionUser
  43. ############################
  44. class SessionUserResponse(Token, UserResponse):
  45. expires_at: Optional[int] = None
  46. @router.get("/", response_model=SessionUserResponse)
  47. async def get_session_user(
  48. request: Request, response: Response, user=Depends(get_current_user)
  49. ):
  50. expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
  51. expires_at = None
  52. if expires_delta:
  53. expires_at = int(time.time()) + int(expires_delta.total_seconds())
  54. token = create_token(
  55. data={"id": user.id},
  56. expires_delta=expires_delta,
  57. )
  58. datetime_expires_at = datetime.datetime.fromtimestamp(
  59. expires_at, datetime.timezone.utc
  60. )
  61. # Set the cookie token
  62. response.set_cookie(
  63. key="token",
  64. value=token,
  65. expires=datetime_expires_at,
  66. httponly=True, # Ensures the cookie is not accessible via JavaScript
  67. samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
  68. secure=WEBUI_SESSION_COOKIE_SECURE,
  69. )
  70. return {
  71. "token": token,
  72. "token_type": "Bearer",
  73. "expires_at": expires_at,
  74. "id": user.id,
  75. "email": user.email,
  76. "name": user.name,
  77. "role": user.role,
  78. "profile_image_url": user.profile_image_url,
  79. }
  80. ############################
  81. # Update Profile
  82. ############################
  83. @router.post("/update/profile", response_model=UserResponse)
  84. async def update_profile(
  85. form_data: UpdateProfileForm, session_user=Depends(get_verified_user)
  86. ):
  87. if session_user:
  88. user = Users.update_user_by_id(
  89. session_user.id,
  90. {"profile_image_url": form_data.profile_image_url, "name": form_data.name},
  91. )
  92. if user:
  93. return user
  94. else:
  95. raise HTTPException(400, detail=ERROR_MESSAGES.DEFAULT())
  96. else:
  97. raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
  98. ############################
  99. # Update Password
  100. ############################
  101. @router.post("/update/password", response_model=bool)
  102. async def update_password(
  103. form_data: UpdatePasswordForm, session_user=Depends(get_current_user)
  104. ):
  105. if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
  106. raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
  107. if session_user:
  108. user = Auths.authenticate_user(session_user.email, form_data.password)
  109. if user:
  110. hashed = get_password_hash(form_data.new_password)
  111. return Auths.update_user_password_by_id(user.id, hashed)
  112. else:
  113. raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_PASSWORD)
  114. else:
  115. raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
  116. ############################
  117. # SignIn
  118. ############################
  119. @router.post("/signin", response_model=SessionUserResponse)
  120. async def signin(request: Request, response: Response, form_data: SigninForm):
  121. if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
  122. if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers:
  123. raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
  124. trusted_email = request.headers[WEBUI_AUTH_TRUSTED_EMAIL_HEADER].lower()
  125. trusted_name = trusted_email
  126. if WEBUI_AUTH_TRUSTED_NAME_HEADER:
  127. trusted_name = request.headers.get(
  128. WEBUI_AUTH_TRUSTED_NAME_HEADER, trusted_email
  129. )
  130. if not Users.get_user_by_email(trusted_email.lower()):
  131. await signup(
  132. request,
  133. response,
  134. SignupForm(
  135. email=trusted_email, password=str(uuid.uuid4()), name=trusted_name
  136. ),
  137. )
  138. user = Auths.authenticate_user_by_trusted_header(trusted_email)
  139. elif WEBUI_AUTH == False:
  140. admin_email = "admin@localhost"
  141. admin_password = "admin"
  142. if Users.get_user_by_email(admin_email.lower()):
  143. user = Auths.authenticate_user(admin_email.lower(), admin_password)
  144. else:
  145. if Users.get_num_users() != 0:
  146. raise HTTPException(400, detail=ERROR_MESSAGES.EXISTING_USERS)
  147. await signup(
  148. request,
  149. response,
  150. SignupForm(email=admin_email, password=admin_password, name="User"),
  151. )
  152. user = Auths.authenticate_user(admin_email.lower(), admin_password)
  153. else:
  154. user = Auths.authenticate_user(form_data.email.lower(), form_data.password)
  155. if user:
  156. expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
  157. expires_at = None
  158. if expires_delta:
  159. expires_at = int(time.time()) + int(expires_delta.total_seconds())
  160. token = create_token(
  161. data={"id": user.id},
  162. expires_delta=expires_delta,
  163. )
  164. datetime_expires_at = datetime.datetime.fromtimestamp(
  165. expires_at, datetime.timezone.utc
  166. )
  167. # Set the cookie token
  168. response.set_cookie(
  169. key="token",
  170. value=token,
  171. expires=datetime_expires_at,
  172. httponly=True, # Ensures the cookie is not accessible via JavaScript
  173. samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
  174. secure=WEBUI_SESSION_COOKIE_SECURE,
  175. )
  176. return {
  177. "token": token,
  178. "token_type": "Bearer",
  179. "expires_at": expires_at,
  180. "id": user.id,
  181. "email": user.email,
  182. "name": user.name,
  183. "role": user.role,
  184. "profile_image_url": user.profile_image_url,
  185. }
  186. else:
  187. raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
  188. ############################
  189. # SignUp
  190. ############################
  191. @router.post("/signup", response_model=SessionUserResponse)
  192. async def signup(request: Request, response: Response, form_data: SignupForm):
  193. if WEBUI_AUTH:
  194. if (
  195. not request.app.state.config.ENABLE_SIGNUP
  196. or not request.app.state.config.ENABLE_LOGIN_FORM
  197. ):
  198. raise HTTPException(
  199. status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
  200. )
  201. else:
  202. if Users.get_num_users() != 0:
  203. raise HTTPException(
  204. status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
  205. )
  206. if not validate_email_format(form_data.email.lower()):
  207. raise HTTPException(
  208. status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
  209. )
  210. if Users.get_user_by_email(form_data.email.lower()):
  211. raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
  212. try:
  213. role = (
  214. "admin"
  215. if Users.get_num_users() == 0
  216. else request.app.state.config.DEFAULT_USER_ROLE
  217. )
  218. hashed = get_password_hash(form_data.password)
  219. user = Auths.insert_new_auth(
  220. form_data.email.lower(),
  221. hashed,
  222. form_data.name,
  223. form_data.profile_image_url,
  224. role,
  225. )
  226. if user:
  227. expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
  228. expires_at = None
  229. if expires_delta:
  230. expires_at = int(time.time()) + int(expires_delta.total_seconds())
  231. token = create_token(
  232. data={"id": user.id},
  233. expires_delta=expires_delta,
  234. )
  235. datetime_expires_at = datetime.datetime.fromtimestamp(
  236. expires_at, datetime.timezone.utc
  237. )
  238. # Set the cookie token
  239. response.set_cookie(
  240. key="token",
  241. value=token,
  242. expires=datetime_expires_at,
  243. httponly=True, # Ensures the cookie is not accessible via JavaScript
  244. samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
  245. secure=WEBUI_SESSION_COOKIE_SECURE,
  246. )
  247. if request.app.state.config.WEBHOOK_URL:
  248. post_webhook(
  249. request.app.state.config.WEBHOOK_URL,
  250. WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
  251. {
  252. "action": "signup",
  253. "message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
  254. "user": user.model_dump_json(exclude_none=True),
  255. },
  256. )
  257. return {
  258. "token": token,
  259. "token_type": "Bearer",
  260. "expires_at": expires_at,
  261. "id": user.id,
  262. "email": user.email,
  263. "name": user.name,
  264. "role": user.role,
  265. "profile_image_url": user.profile_image_url,
  266. }
  267. else:
  268. raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
  269. except Exception as err:
  270. raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
  271. ############################
  272. # AddUser
  273. ############################
  274. @router.post("/add", response_model=SigninResponse)
  275. async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
  276. if not validate_email_format(form_data.email.lower()):
  277. raise HTTPException(
  278. status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
  279. )
  280. if Users.get_user_by_email(form_data.email.lower()):
  281. raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
  282. try:
  283. print(form_data)
  284. hashed = get_password_hash(form_data.password)
  285. user = Auths.insert_new_auth(
  286. form_data.email.lower(),
  287. hashed,
  288. form_data.name,
  289. form_data.profile_image_url,
  290. form_data.role,
  291. )
  292. if user:
  293. token = create_token(data={"id": user.id})
  294. return {
  295. "token": token,
  296. "token_type": "Bearer",
  297. "id": user.id,
  298. "email": user.email,
  299. "name": user.name,
  300. "role": user.role,
  301. "profile_image_url": user.profile_image_url,
  302. }
  303. else:
  304. raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
  305. except Exception as err:
  306. raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
  307. ############################
  308. # GetAdminDetails
  309. ############################
  310. @router.get("/admin/details")
  311. async def get_admin_details(request: Request, user=Depends(get_current_user)):
  312. if request.app.state.config.SHOW_ADMIN_DETAILS:
  313. admin_email = request.app.state.config.ADMIN_EMAIL
  314. admin_name = None
  315. print(admin_email, admin_name)
  316. if admin_email:
  317. admin = Users.get_user_by_email(admin_email)
  318. if admin:
  319. admin_name = admin.name
  320. else:
  321. admin = Users.get_first_user()
  322. if admin:
  323. admin_email = admin.email
  324. admin_name = admin.name
  325. return {
  326. "name": admin_name,
  327. "email": admin_email,
  328. }
  329. else:
  330. raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
  331. ############################
  332. # ToggleSignUp
  333. ############################
  334. @router.get("/admin/config")
  335. async def get_admin_config(request: Request, user=Depends(get_admin_user)):
  336. return {
  337. "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS,
  338. "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP,
  339. "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE,
  340. "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
  341. "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
  342. "ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
  343. }
  344. class AdminConfig(BaseModel):
  345. SHOW_ADMIN_DETAILS: bool
  346. ENABLE_SIGNUP: bool
  347. DEFAULT_USER_ROLE: str
  348. JWT_EXPIRES_IN: str
  349. ENABLE_COMMUNITY_SHARING: bool
  350. ENABLE_MESSAGE_RATING: bool
  351. @router.post("/admin/config")
  352. async def update_admin_config(
  353. request: Request, form_data: AdminConfig, user=Depends(get_admin_user)
  354. ):
  355. request.app.state.config.SHOW_ADMIN_DETAILS = form_data.SHOW_ADMIN_DETAILS
  356. request.app.state.config.ENABLE_SIGNUP = form_data.ENABLE_SIGNUP
  357. if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]:
  358. request.app.state.config.DEFAULT_USER_ROLE = form_data.DEFAULT_USER_ROLE
  359. pattern = r"^(-1|0|(-?\d+(\.\d+)?)(ms|s|m|h|d|w))$"
  360. # Check if the input string matches the pattern
  361. if re.match(pattern, form_data.JWT_EXPIRES_IN):
  362. request.app.state.config.JWT_EXPIRES_IN = form_data.JWT_EXPIRES_IN
  363. request.app.state.config.ENABLE_COMMUNITY_SHARING = (
  364. form_data.ENABLE_COMMUNITY_SHARING
  365. )
  366. request.app.state.config.ENABLE_MESSAGE_RATING = form_data.ENABLE_MESSAGE_RATING
  367. return {
  368. "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS,
  369. "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP,
  370. "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE,
  371. "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
  372. "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
  373. "ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
  374. }
  375. ############################
  376. # API Key
  377. ############################
  378. # create api key
  379. @router.post("/api_key", response_model=ApiKey)
  380. async def create_api_key_(user=Depends(get_current_user)):
  381. api_key = create_api_key()
  382. success = Users.update_user_api_key_by_id(user.id, api_key)
  383. if success:
  384. return {
  385. "api_key": api_key,
  386. }
  387. else:
  388. raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_API_KEY_ERROR)
  389. # delete api key
  390. @router.delete("/api_key", response_model=bool)
  391. async def delete_api_key(user=Depends(get_current_user)):
  392. success = Users.update_user_api_key_by_id(user.id, None)
  393. return success
  394. # get api key
  395. @router.get("/api_key", response_model=ApiKey)
  396. async def get_api_key(user=Depends(get_current_user)):
  397. api_key = Users.get_user_api_key_by_id(user.id)
  398. if api_key:
  399. return {
  400. "api_key": api_key,
  401. }
  402. else:
  403. raise HTTPException(404, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)