forked from open-webui/open-webui
Merge pull request #315 from ollama-webui/account-settings
feat: account settings
This commit is contained in:
commit
87f5e72eeb
5 changed files with 190 additions and 1 deletions
|
@ -64,6 +64,11 @@ class SigninForm(BaseModel):
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePasswordForm(BaseModel):
|
||||||
|
password: str
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
|
||||||
class SignupForm(BaseModel):
|
class SignupForm(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
email: str
|
email: str
|
||||||
|
@ -109,7 +114,16 @@ class AuthsTable:
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def delete_auth_by_id(self, id: str) -> Optional[UserModel]:
|
def update_user_password_by_id(self, id: str, new_password: str) -> bool:
|
||||||
|
try:
|
||||||
|
query = Auth.update(password=new_password).where(Auth.id == id)
|
||||||
|
result = query.execute()
|
||||||
|
|
||||||
|
return True if result == 1 else False
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_auth_by_id(self, id: str) -> bool:
|
||||||
try:
|
try:
|
||||||
# Delete User
|
# Delete User
|
||||||
result = Users.delete_user_by_id(id)
|
result = Users.delete_user_by_id(id)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import uuid
|
||||||
from apps.web.models.auths import (
|
from apps.web.models.auths import (
|
||||||
SigninForm,
|
SigninForm,
|
||||||
SignupForm,
|
SignupForm,
|
||||||
|
UpdatePasswordForm,
|
||||||
UserResponse,
|
UserResponse,
|
||||||
SigninResponse,
|
SigninResponse,
|
||||||
Auths,
|
Auths,
|
||||||
|
@ -53,6 +54,28 @@ async def get_session_user(cred=Depends(bearer_scheme)):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Update Password
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update/password", response_model=bool)
|
||||||
|
async def update_password(form_data: UpdatePasswordForm, cred=Depends(bearer_scheme)):
|
||||||
|
token = cred.credentials
|
||||||
|
session_user = Users.get_user_by_token(token)
|
||||||
|
|
||||||
|
if session_user:
|
||||||
|
user = Auths.authenticate_user(session_user.email, form_data.password)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
hashed = get_password_hash(form_data.new_password)
|
||||||
|
return Auths.update_user_password_by_id(user.id, hashed)
|
||||||
|
else:
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_PASSWORD)
|
||||||
|
else:
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# SignIn
|
# SignIn
|
||||||
############################
|
############################
|
||||||
|
|
|
@ -21,6 +21,9 @@ class ERROR_MESSAGES(str, Enum):
|
||||||
"Your session has expired or the token is invalid. Please sign in again."
|
"Your session has expired or the token is invalid. Please sign in again."
|
||||||
)
|
)
|
||||||
INVALID_CRED = "The email or password provided is incorrect. Please check for typos and try logging in again."
|
INVALID_CRED = "The email or password provided is incorrect. Please check for typos and try logging in again."
|
||||||
|
INVALID_PASSWORD = (
|
||||||
|
"The password provided is incorrect. Please check for typos and try again."
|
||||||
|
)
|
||||||
UNAUTHORIZED = "401 Unauthorized"
|
UNAUTHORIZED = "401 Unauthorized"
|
||||||
ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance."
|
ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance."
|
||||||
ACTION_PROHIBITED = (
|
ACTION_PROHIBITED = (
|
||||||
|
|
|
@ -88,3 +88,34 @@ export const userSignUp = async (name: string, email: string, password: string)
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateUserPassword = async (token: string, password: string, newPassword: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/update/password`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
password: password,
|
||||||
|
new_password: newPassword
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
error = err.detail;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import Advanced from './Settings/Advanced.svelte';
|
import Advanced from './Settings/Advanced.svelte';
|
||||||
import Modal from '../common/Modal.svelte';
|
import Modal from '../common/Modal.svelte';
|
||||||
|
import { updateUserPassword } from '$lib/apis/auths';
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
||||||
|
@ -118,6 +119,11 @@
|
||||||
let authType = 'Basic';
|
let authType = 'Basic';
|
||||||
let authContent = '';
|
let authContent = '';
|
||||||
|
|
||||||
|
// Account
|
||||||
|
let currentPassword = '';
|
||||||
|
let newPassword = '';
|
||||||
|
let newPasswordConfirm = '';
|
||||||
|
|
||||||
// About
|
// About
|
||||||
let ollamaVersion = '';
|
let ollamaVersion = '';
|
||||||
|
|
||||||
|
@ -595,6 +601,31 @@
|
||||||
return models;
|
return models;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePasswordHandler = async () => {
|
||||||
|
if (newPassword === newPasswordConfirm) {
|
||||||
|
const res = await updateUserPassword(localStorage.token, currentPassword, newPassword).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
toast.success('Successfully updated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPassword = '';
|
||||||
|
newPassword = '';
|
||||||
|
newPasswordConfirm = '';
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
`The passwords you entered don't quite match. Please double-check and try again.`
|
||||||
|
);
|
||||||
|
newPassword = '';
|
||||||
|
newPasswordConfirm = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||||
console.log(settings);
|
console.log(settings);
|
||||||
|
@ -845,6 +876,32 @@
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
|
'account'
|
||||||
|
? 'bg-gray-200 dark:bg-gray-700'
|
||||||
|
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTab = 'account';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class=" self-center">Account</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
'about'
|
'about'
|
||||||
|
@ -1817,6 +1874,67 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{:else if selectedTab === 'account'}
|
||||||
|
<form
|
||||||
|
class="flex flex-col h-full text-sm"
|
||||||
|
on:submit|preventDefault={() => {
|
||||||
|
updatePasswordHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" mb-2.5 font-medium">Change Password</div>
|
||||||
|
|
||||||
|
<div class=" space-y-1.5">
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class=" mb-1 text-xs text-gray-500">Current Password</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
type="password"
|
||||||
|
bind:value={currentPassword}
|
||||||
|
autocomplete="current-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class=" mb-1 text-xs text-gray-500">New Password</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
type="password"
|
||||||
|
bind:value={newPassword}
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class=" mb-1 text-xs text-gray-500">Confirm Password</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
type="password"
|
||||||
|
bind:value={newPasswordConfirm}
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 flex justify-end">
|
||||||
|
<button
|
||||||
|
class=" px-4 py-2 text-xs bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-800 text-gray-100 transition rounded-md font-medium"
|
||||||
|
>
|
||||||
|
Update password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
{:else if selectedTab === 'about'}
|
{:else if selectedTab === 'about'}
|
||||||
<div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
|
<div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
|
||||||
<div class=" space-y-3">
|
<div class=" space-y-3">
|
||||||
|
|
Loading…
Reference in a new issue