Skip to content

Commit

Permalink
Merge pull request #36 from LukasMartini/login
Browse files Browse the repository at this point in the history
Login system
  • Loading branch information
ezzhang8 authored Jul 9, 2024
2 parents a906bcf + 2bd7e9f commit 6147981
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 27 deletions.
86 changes: 78 additions & 8 deletions backend/server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
from flask import Flask, Response, jsonify, request
from flask import Flask, request, Response, jsonify, request
from flask_cors import CORS, cross_origin
import uuid
import bcrypt
from datetime import datetime, timedelta

from load_data import create_upload, delete_upload, update_upload_status
from db_commands import get_db_connection, get_hand_count, get_cash_flow, one_time_hand_info, player_actions_in_hand, player_cards_in_hand
Expand All @@ -15,11 +18,22 @@
app.config['CORS_HEADERS'] = 'Content-Type'
app.config['UPLOAD_FOLDER'] = './uploads/'


@app.route('/')
@cross_origin()
def homepage():
return Response(status=200)
def auth(auth_header):
cur = conn.cursor()

try:
parts = auth_header.split()
if len(parts) == 2 and parts[0].lower() == 'bearer':
token = parts[1]
cur.execute("EXECUTE authorize(%s)", (token,))
result = cur.fetchall()
cur.close()
return result[0][0]
else:
raise Exception("Invalid authorization token.")
except Exception as e:
print(e)
raise e

@app.route('/api/hand_summary/<int:id>', methods=['GET'])
@cross_origin()
Expand Down Expand Up @@ -62,6 +76,62 @@ def cash_flow(id: int, limit: int, offset: int) -> Response:

return jsonify(data), 200

@app.route("/api/authorize", methods=['POST'])
@cross_origin()
def authorize() -> Response:
try:
user_id = auth(request.headers.get("Authorization"))
return jsonify({"success": True, "user_id": user_id}), 200
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 403

@app.route("/api/signup", methods=['POST'])
@cross_origin()
def signup() -> Response:
try:
if request.is_json:
data = request.get_json() # Accessing JSON data from the request body
# Process your data (example: registering a user)
# Assuming data contains 'username' and 'email'
username = data.get('username')
email = data.get('email')
salt = bcrypt.gensalt()
password = data.get('password')
hashed_password = bcrypt.hashpw(password.encode(), salt)
hashed_password = hashed_password.decode('utf-8')
salt = salt.decode('utf-8')
cur.execute("EXECUTE createUser (%s, %s, %s, %s, %s, %s)", (username, email, hashed_password, str(uuid.uuid4()), datetime.now() + timedelta(days=1), salt))
conn.commit()
return jsonify('{"success": true}'), 200

except Exception as e:
print(e)
return jsonify('{"success": false}'), 400

@app.route("/api/login", methods=['POST'])
def login():
try:
if request.is_json:
data = request.get_json() # Accessing JSON data from the request body
password = data.get('password')
username = data.get('username')
cur.execute("EXECUTE login(%s)", (username,))
conn.commit()
result = cur.fetchall()
hashed_password = bcrypt.hashpw(password.encode(), result[0][0].encode())
if (hashed_password.decode("utf-8") == result[0][1]):
token = result[0][2]
if result[0][5] == False:
token = str(uuid.uuid4())
cur.execute("EXECUTE renewToken(%s, %s, %s)", (username, token, datetime.now() + timedelta(days=1)))
conn.commit()
return jsonify({"success": True, "token": token, "username": result[0][3], "email": result[0][4]}), 200
else:
return jsonify({"success": False, "error": "Incorrect username or password"}), 403
except Exception as e:
print(e)
return jsonify({"success": False, "error": "Bad Request"}), 400

@app.route('/api/upload', methods=['POST'])
@cross_origin()
def file_upload():
Expand All @@ -86,7 +156,7 @@ def file_upload():

if __name__ == '__main__':
cur.execute(open('./sql/R6/fetch_hand_query_templates.sql').read())
cur.execute(open('./sql/R10/authorization.sql').read())
app.run(host="localhost", port=5001, debug=True)

cur.close()
conn.close()
conn.close()
15 changes: 15 additions & 0 deletions backend/sql/R10/authorization.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
PREPARE createUser(text, text, text, text, timestamp, text) AS
INSERT INTO users(username, email, password_hash, created_at, token, expiry_date, salt) VALUES($1, $2, $3, NOW(), $4, $5, $6);

PREPARE login(text) AS
SELECT salt, password_hash, token, username, email, expiry_date > NOW() FROM users
WHERE email = $1 OR username = $1;

PREPARE renewToken(text, text, timestamp) AS
UPDATE users
SET token = $2, expiry_date = $3
WHERE email = $1 OR username = $1;

PREPARE authorize(text) AS
SELECT id FROM users
WHERE token = $1 AND expiry_date > NOW()
3 changes: 2 additions & 1 deletion backend/sql/create_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ CREATE TABLE users (
password_hash CHAR(60), -- Assuming bcrypt hash which outputs 60 characters
salt CHAR(29), -- Storing bcrypt salt, which is 29 characters long
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
token CHAR(36)
token CHAR(36),
expiry_date TIMESTAMP
);

CREATE TABLE uploads (
Expand Down
5 changes: 0 additions & 5 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import './App.css';
import 'dotenv/config'
import { AuthProvider } from '@/components/auth/AuthContext';

const API = process.env.API;

Expand Down Expand Up @@ -36,12 +37,13 @@ function App() {
}

return (
<div className="App">
<header className="App-header">
<h1>Hand test</h1>

</header>
</div>
<AuthProvider>
<div className="App">
<header className="App-header">
<h1>Hand test</h1>
</header>
</div>
</AuthProvider>
);
}

Expand Down
15 changes: 9 additions & 6 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Navbar from "@/components/Navbar";
import { AuthProvider } from '@/components/auth/AuthContext';

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -16,11 +17,13 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en" className="bg-[#2C2C2C]">
<body className={inter.className}>
<Navbar />
{children}
</body>
</html>
<AuthProvider>
<html lang="en" className="bg-[#2C2C2C]">
<body className={inter.className}>
<Navbar />
{children}
</body>
</html>
</AuthProvider>
);
}
67 changes: 67 additions & 0 deletions frontend/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

import { Button } from '@/components/ui/button';
import { useAuth } from '@/components/auth/AuthContext';

const LoginPage = () => {
const user = useAuth();

const handleSubmit = async (event: any) => {
event.preventDefault();
const formData = new FormData(event.target);
console.log(formData);
const response = await fetch(`http://localhost:5001/api/login`, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(Object.fromEntries(formData))
});

const result = await response.json();

if (result.success) {
user.login(result.token, result.email, result.username);
}
};
console.log(user.auth);

return (
<div className="bg-[#2C2C2C] text-white px-64">
{user.auth.token && (
<div>
<h1 className="text-xl text-center font-bold pt-8">
Logged in!
</h1>

<Button className="w-full" onClick={()=>user.logout()}>Logout</Button>
</div>
)}
{!user.auth.token && (
<div>
<h1 className="text-xl text-center font-bold pt-8">
Login to PokerReplay
</h1>
<br/><br/>
<form encType='multipart/form-data' onSubmit={handleSubmit} className="mx-auto max-w-xs p-8 space-y-4 bg-[#232323] rounded-lg shadow-md">
<input
className="w-full bg-gray-800 rounded-md border border-gray-400 py-2 px-4 text-sm text-white placeholder-gray-400"
type="text"
name="username"
placeholder="Email or username"
/>
<input
className="w-full bg-gray-800 rounded-md border border-gray-400 py-2 px-4 text-sm text-white placeholder-gray-400"
type="password"
name="password"
placeholder="Password"
/>
<Button className="w-full" type='submit'>Login</Button>
</form>
</div>
)}
</div>
)
}

export default LoginPage
76 changes: 76 additions & 0 deletions frontend/src/app/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client';

import { Button } from '@/components/ui/button';

const handleSubmit = async (event: any) => {
event.preventDefault();
const formData = new FormData(event.target);
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_\-\+=~`{}$begin:math:display$$end:math:display$|\\:;"'<,>.?/]).{12,}$/;

if (!emailRegex.test(formData.get("email") as string)) {
alert("Please enter a valid email address ([email protected])");
return;
}
if (!passwordRegex.test(formData.get("password") as string)) {
alert("Please enter a minimum 12 character password including a number and special character.");
return;
}
if (formData.get("password") != formData.get("confirm_password")) {
alert("Confirm Password doesn't match your password");
return;
}


const response = await fetch(`http://localhost:5001/api/signup`, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(Object.fromEntries(formData))
});

window.location.reload();

console.log(await response.json());
};

const SignupPage = () => {
return (
<div className="bg-[#2C2C2C] text-white px-64">
<h1 className="text-xl text-center font-bold pt-8">
Sign up for PokerReplay
</h1>
<br/><br/>
<form encType='multipart/form-data' onSubmit={handleSubmit} className="mx-auto max-w-xs p-8 space-y-4 bg-[#232323] rounded-lg shadow-md">
<input
className="w-full bg-gray-800 rounded-md border border-gray-400 py-2 px-4 text-sm text-white placeholder-gray-400"
type="email"
name="email"
placeholder="Email address"
/>
<input
className="w-full bg-gray-800 rounded-md border border-gray-400 py-2 px-4 text-sm text-white placeholder-gray-400"
type="text"
name="username"
placeholder="Username"
/>
<input
className="w-full bg-gray-800 rounded-md border border-gray-400 py-2 px-4 text-sm text-white placeholder-gray-400"
type="password"
name="password"
placeholder="Password"
/>
<input
className="w-full bg-gray-800 rounded-md border border-gray-400 py-2 px-4 text-sm text-white placeholder-gray-400"
type="password"
name="confirm_password"
placeholder="Confirm Password"
/>
<Button className="w-full" type='submit'>Sign Up</Button>
</form>
</div>
)
}

export default SignupPage
Loading

0 comments on commit 6147981

Please sign in to comment.