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

Develop v1 #34

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 4 additions & 2 deletions backend/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ def create_app():

CORS(app, resources={
r"/*": {
"origins": ["http://localhost:5173"],
"origins": ["http://localhost:5173", "http://localhost:3000"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"]
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True,
"expose_headers": ["Authorization"]
}
})

Expand Down
91 changes: 56 additions & 35 deletions backend/app/views/view_user.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
from flask import Blueprint, jsonify, request
from flask import Blueprint, jsonify, request, make_response
from werkzeug.security import generate_password_hash, check_password_hash
from flask_jwt_extended import create_access_token
from app.models.user import User
from app.models import db
from app.models.enums import GenderEnum
from flask_cors import CORS
from datetime import timedelta
from flask import jsonify, request
from typing import Tuple, Union
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
user_bp = Blueprint('user', __name__)

CORS(user_bp, resources={
r"/*": {
"origins": ["http://localhost:5173"],
"origins": ["http://localhost:5173", "http://localhost:3000"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"]
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True,
"expose_headers": ["Authorization"]
}
})

Expand Down Expand Up @@ -52,7 +62,6 @@ def create_user():
'user': {
'username': user.username,
'email': user.email,
'admin': user.admin,
'gender': user.gender.value
}
}), 201
Expand All @@ -62,39 +71,51 @@ def create_user():
return jsonify({'error': str(e)}), 400

@user_bp.route("/login", methods=['POST', 'OPTIONS'])
def login():
@limiter.limit("5 per minute")
def login() -> Tuple[Union[str, dict], int]:
if request.method == 'OPTIONS':
return '', 204
response = make_response()
response.headers.add('Access-Control-Allow-Origin', 'http://localhost:5173')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
response.headers.add('Access-Control-Allow-Credentials', 'true')
return response

if request.method == 'POST':
try:
data = request.get_json() if request.is_json else request.form
username = data.get('username')
password = data.get('password')
try:
data = request.get_json() if request.is_json else request.form
email = data.get('email')
password = data.get('password')

if not username or not password:
return jsonify({'error': 'Username and password are required'}), 400
if not email or not password:
return jsonify({'error': 'Email and password are required'}), 400

user = User.query.filter_by(username=username).first()

if user and check_password_hash(user.password, password):
access_token = create_access_token(
identity=user.id,
expires_delta=timedelta(days=7)
)

return jsonify({
'message': 'Login successful',
'token': access_token,
'user': {
'id': user.id,
'username': user.username,
'email': user.email,
'admin': user.admin
}
}), 200
else:
return jsonify({'error': 'Invalid credentials'}), 401
user = User.query.filter_by(email=email.lower().strip()).first()

if not user:
return jsonify({'error': 'Invalid credentials'}), 401

if not check_password_hash(user.password, password):
return jsonify({'error': 'Invalid credentials'}), 401

access_token = create_access_token(
identity=user.id,
expires_delta=timedelta(days=7)
)

response = jsonify({
'message': 'Login successful',
'token': access_token,
'user': {
'id': user.id,
'username': user.username,
'email': user.email,
'admin': user.admin
}
})
response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5173'
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Authorization'] = f'Bearer {access_token}'
return response, 200

except Exception as e:
return jsonify({'error': str(e)}), 500
except Exception as e:
return jsonify({'error': f'Login failed: {str(e)}'}), 500
2 changes: 2 additions & 0 deletions frontend/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Mapas from './pages/Mapas';
import Noticias from './pages/Noticias';
import Login from './pages/Auth/Login';
import Register from './pages/Auth/Register';
import Status from './pages/Status';
import Footer from './components/footer';

const AppRouter = () => {
Expand All @@ -21,6 +22,7 @@ const AppRouter = () => {
<Route path="/noticias" element={<Noticias />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/status" element={<Status />} />
</Routes>
<Footer />
</>
Expand Down
110 changes: 94 additions & 16 deletions frontend/src/Context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import React, { createContext, useContext } from "react"
import React, { createContext, useContext, useEffect } from "react"
import { useState } from "react";

export interface User{
Expand All @@ -12,7 +12,10 @@ export interface User{

interface AuthContextType{
user: User | null;
signup: (username: string, email: string, password: string) => Promise<{ ok: boolean; error?: string}>;
signup: (username: string, email: string, password: string) => Promise<{ ok: boolean; error?: string}>;
login: (email: string, password: string) => Promise<{ ok: boolean; error?: string }>;
logout: () => void;
isReady: boolean;
}

interface AuthProviderProps{
Expand All @@ -23,6 +26,71 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);

const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [error, setError] = useState<string>("");
const [token, setToken] = useState<string | null>(null);
const [isReady, setIsReady] = useState(false);

useEffect(() => {
const storedToken = localStorage.getItem('token');
const storedUser = localStorage.getItem('user');

if (storedToken){
setToken(storedToken);
if (storedUser){
setUser(JSON.parse(storedUser));
}
}

setIsReady(true);
}, []);

const login = async (email: string, password: string): Promise<{ ok: boolean; error?: string }> => {
const data = { email, password };

try {
const response = await fetch('http://localhost:5000/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include',
mode: 'cors'
});

if (!response.ok) {
const errorData = await response.json();
setError(errorData.error || 'Login failed');
return { ok: false, error: errorData.error || 'Login failed' };
}

const result = await response.json();
const token = response.headers.get('Authorization')?.split(' ')[1] || result.token;

if (!token) {
return { ok: false, error: 'No token received' };
}

localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(result.user));
setToken(token);
setUser(result.user);
return { ok: true };

} catch (error) {
const errorMessage = 'Network error: Failed to connect to server';
console.error('Login error:', error);
setError(errorMessage);
return { ok: false, error: errorMessage };
}
};

const logout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
setToken(null);
setUser(null);
};

const SignUp = async (username: string, email: string, password: string) => {
const data = { username, email, password}
Expand All @@ -33,23 +101,33 @@ const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
if (response.ok) {
return { ok: true}
}
else{
const result = await response.json();
return { ok: false, error: result.error }
body: JSON.stringify(data),
credentials: 'include'
});

if (!response.ok) {
const errorData = await response.json();
console.error('Registration failed:', errorData);
return { ok: false, error: errorData.error || 'Registration failed' };
}
} catch(error){
return { ok: false, error: 'Error'}

const result = await response.json();
return { ok: true };
} catch(error) {
console.error('Registration error:', error);
return { ok: false, error: 'Error connecting to server' };
}
}

return (
<AuthContext.Provider value={{ user, signup: SignUp }}>
{children}
<AuthContext.Provider value={{
user,
signup: SignUp,
login,
logout,
isReady
}}>
{isReady ? children : null}
</AuthContext.Provider>
);
}
Expand All @@ -59,7 +137,7 @@ const useAuth = () => {
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return [context.signup, context.user] as const;
return context;
}

export { AuthContext, AuthProvider, useAuth };
export { AuthContext, AuthProvider, useAuth };
12 changes: 11 additions & 1 deletion frontend/src/components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useState } from 'react';
import { useAuth } from '../Context/AuthContext';

export default function NavBar() {
const [isOpen, setIsOpen] = useState(false);
const auth = useAuth();

return (
<div className="navbar relative" style={{ backgroundColor: '#1D232A' }}>
Expand Down Expand Up @@ -44,7 +46,15 @@ export default function NavBar() {
</ul>
</div>
<div className="navbar-end">
<a href='/login' className="btn bg-blue-500 hover:bg-blue-600 text-white border-none">Login</a>
{auth.user ? (
<a onClick={auth.logout} href='/login' className="btn bg-red-500 hover:bg-red-600 text-white border-none">
Logout
</a>
) : (
<a href='/login' className="btn bg-blue-500 hover:bg-blue-600 text-white border-none">
Login
</a>
)}
</div>
</div>
);
Expand Down
Loading