Skip to content

Commit

Permalink
create admin user on startup in prod, create change password page tha…
Browse files Browse the repository at this point in the history
…t is used whenever a users requires a password update, fix bug where fields are not reset upon successfuly password change
  • Loading branch information
liberty-rising committed Dec 26, 2023
1 parent e6ef757 commit 828bf00
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 43 deletions.
1 change: 1 addition & 0 deletions backend/envs/prod/initialization/setup_prod_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def create_admin_user():
email="[email protected]",
organization_id=1,
role="admin",
requires_password_update=True,
)

with DatabaseManager() as session:
Expand Down
6 changes: 5 additions & 1 deletion backend/models/user.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import Enum
from pydantic import BaseModel, EmailStr, Field, validator
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.sql import func
from typing import Optional

Expand Down Expand Up @@ -29,6 +29,7 @@ class User(Base):
hashed_password = Column(String)
organization_id = Column(Integer, ForeignKey("organizations.id"), nullable=True)
role = Column(String, nullable=True)
requires_password_update = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), default=func.now())
refresh_token = Column(Text, nullable=True)

Expand All @@ -50,6 +51,7 @@ class UserCreate(BaseModel):
password: str
organization_id: Optional[int] = None
role: Optional[str] = None
requires_password_update: Optional[bool] = False

@validator("password")
def validate_password(cls, v):
Expand Down Expand Up @@ -85,6 +87,7 @@ class UserOut(BaseModel):
email: EmailStr
organization_id: Optional[int]
role: Optional[str]
requires_password_update: bool


class UserRole(str, Enum):
Expand All @@ -109,6 +112,7 @@ class UserUpdate(BaseModel):
username: str
organization_id: Optional[int]
role: Optional[UserRole]
requires_password_update: Optional[bool] = False


class ChangePassword(BaseModel):
Expand Down
12 changes: 9 additions & 3 deletions backend/routes/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ async def get_users(current_admin_user: User = Depends(get_current_admin_user)):

@user_router.get("/users/me/", response_model=UserOut)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
user_out = UserOut(
id=current_user.id,
username=current_user.username,
email=current_user.email,
organization_id=current_user.organization_id,
role=current_user.role,
requires_password_update=current_user.requires_password_update,
)
return user_out


@user_router.get("/users/roles/")
Expand Down Expand Up @@ -57,10 +65,8 @@ async def update_user(
async def change_user_password(
change_password: ChangePassword, current_user: User = Depends(get_current_user)
):
print("change_password", change_password)
# Verify old password
if not verify_password(change_password.old_password, current_user.hashed_password):
print("HELLO")
raise HTTPException(status_code=400, detail="Invalid old password")

with DatabaseManager() as session:
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import AboutPage from './pages/about/AboutPage';
import AdminPage from './pages/admin/AdminPage';
import AnalyticsPage from './pages/analytics/AnalyticsPage';
import BlogPage from './pages/blog/BlogPage';
import ChangePasswordPage from './pages/change-password/ChangePasswordPage';
import CreateChartPage from './pages/charts/CreateChartPage';
import CreateDashboardPage from './pages/dashboards/CreateDashboard';
import DashboardMenuPage from './pages/dashboards/DashboardsMenuPage';
Expand Down Expand Up @@ -60,6 +61,7 @@ function App() {
element={isAuthenticated ? <Navigate to ="/dashboards" /> :
<LandingLayout><LoginPage /></LandingLayout>}
/>
<Route path="/change-password" element={<RequireAuth><LandingLayout><ChangePasswordPage /></LandingLayout></RequireAuth>} />
<Route path="/register" element={<LandingLayout><RegisterPage /></LandingLayout>} />
<Route path="/pricing" element={<LandingLayout><PricingPage /></LandingLayout>} />
<Route path="/blog" element={<LandingLayout><BlogPage /></LandingLayout>} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ChangePassword = ({ handleChangePassword, errorMessage, successMessage })
const handleSubmit = async (event) => {
event.preventDefault();
const success = await handleChangePassword(oldPassword, newPassword, confirmPassword);
console.log('handleChangePassword result:', success);
if (success) {
setOldPassword('');
setNewPassword('');
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/pages/change-password/ChangePasswordPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom';
import { Box, Container, Typography } from '@mui/material'
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import ChangePassword from '../../components/change-password/ChangePassword';
import { updateUserPassword } from '../../utils/updateUserPassword';

function ChangePasswordPage() {
const navigate = useNavigate();
const [errorMessage, setErrorMessage] = useState('');
const [successMessage, setSuccessMessage] = useState('');

const handleChangePassword = async (oldPassword, newPassword, confirmPassword) => {
const success = await updateUserPassword(oldPassword, newPassword, confirmPassword, setErrorMessage, setSuccessMessage);
if (success) {
navigate('/dashboards');
}
};

return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<LockOutlinedIcon color="secondary" sx={{ m: 1, bgcolor: 'background.paper', borderRadius: '50%' }} />
{/* <Typography component="h1" variant="h5">
Change Password
</Typography> */}
<ChangePassword handleChangePassword={handleChangePassword} errorMessage={errorMessage} successMessage={successMessage} />
</Box>
</Container>
)
}

export default ChangePasswordPage
11 changes: 10 additions & 1 deletion frontend/src/pages/login/LoginPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,16 @@ function LoginPage({ onLogin }) {

if (response.status === 200) {
updateAuth(true);
navigate('/dashboards');
const userResponse = await axios.get(`${API_URL}users/me/`, {
headers: {
'Authorization': `Bearer ${response.data.access_token}`
}
});
if (userResponse.data.requires_password_update) {
navigate('/change-password');
} else {
navigate('/dashboards');
}
}
} catch (error) {
if (error.response && error.response.status === 401) {
Expand Down
42 changes: 4 additions & 38 deletions frontend/src/pages/user/UserPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import EmailIcon from '@mui/icons-material/Email';
import BusinessIcon from '@mui/icons-material/Business';
import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import { Alert, Box, CircularProgress, Grid, Snackbar, Typography } from '@mui/material';
import { Alert, Box, CircularProgress, Grid, Typography } from '@mui/material';
import InfoCard from './InfoCard';
import ChangePassword from './ChangePassword';
import ChangePassword from '../../components/change-password/ChangePassword';
import { updateUserPassword } from '../../utils/updateUserPassword';
import { API_URL } from '../../utils/constants';

const UserPage = () => {
Expand Down Expand Up @@ -43,42 +44,7 @@ const UserPage = () => {
}, []);

const handleChangePassword = async (oldPassword, newPassword, confirmPassword) => {
if (newPassword !== confirmPassword) {
setErrorMessage('Passwords do not match!');
return;
}

try {
const response = await axios.put(`${API_URL}users/change-password/`, { old_password: oldPassword, new_password: newPassword });

if (response.status === 200) {
setSuccessMessage('Password changed successfully');
setErrorMessage('');
return true;
} else {
setErrorMessage('Failed to change password!');
return false;
}
} catch (error) {
if (error.response) {
if (error.response.status === 400) {
setErrorMessage(error.response.data.detail);
} else if (error.response.data && error.response.data.detail && error.response.data.detail[0]) {
let errorMessage = error.response.data.detail[0].msg;
if (typeof errorMessage === 'string') {
errorMessage = errorMessage.replace('Value error, ', '');
setErrorMessage(errorMessage);
}
}
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message)
}
return false;
}
return await updateUserPassword(oldPassword, newPassword, confirmPassword, setErrorMessage, setSuccessMessage);
};

return (
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/utils/updateUserPassword.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import axios from 'axios';
import { API_URL } from './constants';

export const updateUserPassword = async (oldPassword, newPassword, confirmPassword, setErrorMessage, setSuccessMessage) => {
if (newPassword !== confirmPassword) {
setErrorMessage('Passwords do not match!');
return false;
}

try {
const response = await axios.put(`${API_URL}users/change-password/`, { old_password: oldPassword, new_password: newPassword });

console.log('Server response status:', response.status); // Add this line

if (response.status === 200) {
console.log('Password changed successfully')
setSuccessMessage('Password changed successfully');
setErrorMessage('');
return true;
} else {
setErrorMessage('Failed to change password!');
setSuccessMessage('');
return false;
}
} catch (error) {
if (error.response) {
if (error.response.status === 400) {
setErrorMessage(error.response.data.detail);
} else if (error.response.data && error.response.data.detail && error.response.data.detail[0]) {
let errorMessage = error.response.data.detail[0].msg;
if (typeof errorMessage === 'string') {
setErrorMessage(errorMessage);
}
}
}
}
setErrorMessage(errorMessage);
setSuccessMessage('');
return false;
};

0 comments on commit 828bf00

Please sign in to comment.