Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prod admin user #138

Merged
merged 3 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added backend/envs/dev/__init__.py
Empty file.
Empty file.
Empty file added backend/envs/prod/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions backend/envs/prod/initialization/setup_prod_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from databases.database_manager import DatabaseManager
from databases.user_manager import UserManager
from models.user import User
from security import get_password_hash
from utils.utils import get_app_logger

logger = get_app_logger(__name__)


def create_admin_user():
"""Creates an admin user if it doesn't already exist."""
admin_user = User(
username="admin",
hashed_password=get_password_hash("admin"),
email="[email protected]",
organization_id=1,
role="admin",
requires_password_update=True,
)

with DatabaseManager() as session:
user_manager = UserManager(session)
existing_user = user_manager.get_user_by_username(admin_user.username)
if not existing_user:
user_manager.create_user(admin_user)
logger.debug("Admin user created.")
else:
logger.debug("Admin user already exists.")
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
10 changes: 8 additions & 2 deletions backend/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
>>> run_startup_routines()
"""
from envs.dev.initialization.setup_dev_environment import (
create_admin_user,
create_admin_user as dev_create_admin_user,
create_sample_dashboard,
create_sample_organization,
create_sample_dataprofile,
)
from envs.prod.initialization.setup_prod_environment import (
create_admin_user as prod_create_admin_user,
)
from envs.dev.utils import seed_db
from settings import APP_ENV, JWT_SECRET_KEY
from utils.utils import get_app_logger
Expand All @@ -27,11 +30,14 @@ def run_startup_routines():

if APP_ENV == "development":
create_sample_organization()
create_admin_user()
dev_create_admin_user()
seed_db()
create_sample_dashboard()
create_sample_dataprofile()

if APP_ENV == "prod":
prod_create_admin_user()


def check_jwt_secret_key():
if APP_ENV != "development" and JWT_SECRET_KEY == "mysecretkey":
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;
};
Loading