Skip to content
This repository has been archived by the owner on Dec 3, 2024. It is now read-only.

Commit

Permalink
Closes #4 and #12
Browse files Browse the repository at this point in the history
  • Loading branch information
aliirz committed Sep 23, 2024
1 parent 1c99774 commit cb3b0e1
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 56 deletions.
55 changes: 49 additions & 6 deletions PehchanPortal/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def get_db():
return db

def init_db():
"""Initialize the database schema."""
"""Initialize the database schema with Keycloak User ID."""
with sqlite3.connect(DATABASE) as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS users (
Expand All @@ -19,24 +19,28 @@ def init_db():
email TEXT UNIQUE NOT NULL,
phone TEXT NOT NULL,
cnic TEXT UNIQUE NOT NULL,
keycloak_user_id TEXT UNIQUE, -- Store Keycloak UUID
gender TEXT DEFAULT 'N/A',
mothers_name TEXT DEFAULT 'N/A',
address TEXT DEFAULT 'N/A',
date_of_birth TEXT DEFAULT 'N/A',
avatar TEXT DEFAULT 'default_avatar.png', -- Avatar field
avatar TEXT DEFAULT 'default_avatar.png',
verified BOOLEAN NOT NULL DEFAULT 0
)
''')


def add_user(full_name, email, phone, cnic):

def add_user(full_name, email, phone, cnic, keycloak_user_id):
"""Add a new user to the database, including Keycloak User ID."""
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO users (full_name, email, phone, cnic)
VALUES (?, ?, ?, ?)
''', (full_name, email, phone, cnic))
INSERT INTO users (full_name, email, phone, cnic, keycloak_user_id)
VALUES (?, ?, ?, ?, ?)
''', (full_name, email, phone, cnic, keycloak_user_id))
return cursor.lastrowid


def update_user_profile(user_id, full_name, phone, email, cnic, gender, mothers_name, address, date_of_birth):
"""Update a user's profile information."""
Expand Down Expand Up @@ -66,6 +70,29 @@ def get_user_by_email(email):
'verified': user[6]
}
return None

def get_user_by_id(user_id):
"""Retrieve a user by their ID."""
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
user = cursor.fetchone()
if user:
return {
'id': user[0],
'full_name': user[1],
'email': user[2],
'phone': user[3],
'cnic': user[4],
'gender': user[5],
'mothers_name': user[6],
'address': user[7],
'date_of_birth': user[8],
'avatar': user[9],
'verified': user[10]
}
return None


def verify_user(user_id):
with sqlite3.connect(DATABASE) as conn:
Expand All @@ -82,3 +109,19 @@ def update_avatar(user_id, avatar_filename):
''', (avatar_filename, user_id))
return cursor.rowcount > 0

def get_user_by_keycloak_id(keycloak_user_id):
"""Retrieve a user by their Keycloak user ID."""
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE keycloak_user_id = ?', (keycloak_user_id,))
user = cursor.fetchone()
if user:
return {
'id': user[0],
'full_name': user[1],
'email': user[2],
'phone': user[3],
'cnic': user[4],
'avatar': user[9]
}
return None
148 changes: 102 additions & 46 deletions PehchanPortal/main.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
from database import init_db, add_user, get_user_by_email, verify_user, update_avatar
from database import init_db, add_user, get_user_by_email, verify_user, update_avatar, update_user_profile, get_user_by_keycloak_id
import re
=import os
import os
from werkzeug.utils import secure_filename
import requests
from keycloak import KeycloakAdmin
from keycloak import KeycloakOpenIDConnection
from keycloak import KeycloakAdmin, KeycloakOpenIDConnection, KeycloakOpenID



KEYCLOAK_URL = os.getenv('KEYCLOAK_URL', 'http://keycloak:8080')
KEYCLOAK_CLIENT_ID = os.getenv('KEYCLOAK_CLIENT_ID', 'pportal')
KEYCLOAK_CLIENT_SECRET = 'vB9uruPX8DGlglHFKVMGPwRrNE19NTrW'
KEYCLOAK_REALM = os.getenv('KEYCLOAK_REALM', 'pehchan')

keycloak_connection = KeycloakOpenIDConnection(
server_url=os.getenv('KEYCLOAK_URL', 'http://keycloak:8080'),
Expand All @@ -16,6 +22,16 @@
client_secret_key='vB9uruPX8DGlglHFKVMGPwRrNE19NTrW',
verify=True)

# Set up KeycloakOpenID instance
keycloak_openid = KeycloakOpenID(
server_url="http://keycloak:8080", # Access via service name inside Docker
client_id="pportal",
realm_name="pehchan",
client_secret_key="vB9uruPX8DGlglHFKVMGPwRrNE19NTrW"
)




keycloak_admin = KeycloakAdmin(connection=keycloak_connection)

Expand All @@ -29,10 +45,6 @@
app = Flask(__name__)
app.secret_key = 'your_secret_key_here' # In production, use a proper secret key

KEYCLOAK_URL = os.getenv('KEYCLOAK_URL', 'http://keycloak:8080')
KEYCLOAK_CLIENT_ID = os.getenv('KEYCLOAK_CLIENT_ID', 'pportal')
KEYCLOAK_CLIENT_SECRET = 'vB9uruPX8DGlglHFKVMGPwRrNE19NTrW'
KEYCLOAK_REALM = os.getenv('KEYCLOAK_REALM', 'pehchan')

init_db()

Expand Down Expand Up @@ -110,27 +122,33 @@ def register():
]
}

print('user data', user_data)

# Try creating the user in Keycloak
try:
user_created = keycloak_admin.create_user(user_data)#create_user_in_keycloak(user_data)
if user_created:
# also store in db
add_user(full_name, email, phone, cnic)
return jsonify({'success': True, 'message': 'Registration successful! Please log in.'})
else:
return jsonify({'success': False, 'message': 'Failed to register user in Keycloak'})
# Create the user in Keycloak
keycloak_admin.create_user(user_data)

# Retrieve the user by email to get Keycloak's unique user ID
users = keycloak_admin.get_users({"email": email})
if not users:
return jsonify({'success': False, 'message': 'User not found in Keycloak'})

keycloak_user_id = users[0]['id'] # Keycloak's unique user ID

# Also store the user in your database with the Keycloak user ID
add_user(full_name, email, phone, cnic, keycloak_user_id=keycloak_user_id)

return jsonify({'success': True, 'message': 'Registration successful! Please log in.'})

except Exception as e:
return jsonify({'success': False, 'message': str(e)})



# Login route
@app.route('/login')
def login():
keycloak_login_url = f'http://localhost:8080/realms/{KEYCLOAK_REALM}/protocol/openid-connect/auth'
keycloak_login_url = f'http://keycloak:8080/realms/{KEYCLOAK_REALM}/protocol/openid-connect/auth'
client_id = KEYCLOAK_CLIENT_ID
redirect_uri = 'http://localhost:5002/dashboard'
redirect_uri = 'http://pechchan-mvp-pehchan-portal-1:5002/callback'
state = os.urandom(8).hex()
nonce = os.urandom(8).hex()
response_type = 'code'
Expand All @@ -142,45 +160,83 @@ def login():

@app.route('/dashboard')
def dashboard():
# Ensure the user is logged in
user_id = session.get('user_id')
if not user_id:
return redirect('/login')

# Fetch the user from the database
user = get_user_by_keycloak_id(user_id)

if not user:
return "User not found", 404

# Render the dashboard with the user's profile
return render_template('dashboard.html', user=user)


@app.route('/callback')
def callback():
code = request.args.get('code')
print('Code from Keyclock', code)

if not code:
return "Error: No authorization code returned from Keycloak", 400

# Exchange authorization code for tokens
token_url = f'{KEYCLOAK_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token'
data = {
'grant_type': 'authorization_code',
'client_id': KEYCLOAK_CLIENT_ID,
'client_secret': KEYCLOAK_CLIENT_SECRET,
'code': code,
'redirect_uri': 'http://localhost:5002/dashboard'
}

headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
try:
# Use python-keycloak to exchange the authorization code for tokens
token = keycloak_openid.token(
grant_type='authorization_code',
code=code,
redirect_uri='http://pechchan-mvp-pehchan-portal-1:5002/callback'
)

# Store tokens in session
session['access_token'] = token['access_token']
session['refresh_token'] = token['refresh_token']
session['id_token'] = token['id_token']

# Print the access token to the console for verification
print(f"Access Token: {session['access_token']}")

# Use the access token to get user info from Keycloak
user_info = keycloak_openid.userinfo(token=session['access_token'])

keycloak_user_id = user_info['sub'] # Keycloak's unique user ID

# Store the user_id in the session for future use
session['user_id'] = keycloak_user_id

return redirect('/dashboard')

except Exception as e:
print(f"Error fetching tokens or user info: {str(e)}")
return f"Error fetching tokens or user info: {str(e)}", 500

# Send request to Keycloak to get tokens
token_response = requests.post(token_url, data=data, headers=headers)

if token_response.status_code != 200:
return f"Error exchanging code for tokens: {token_response.text}", 500

tokens = token_response.json()

# Store the tokens in the session (just for demonstration; adjust as per your needs)
session['access_token'] = tokens.get('access_token')
session['refresh_token'] = tokens.get('refresh_token')
session['id_token'] = tokens.get('id_token')

# Render callback view (You can modify to redirect elsewhere)
return render_template('dashboard.html', code=code)

@app.route('/logout')
def logout():
# Keycloak logout URL
keycloak_logout_url = f'http://keycloak:8080/realms/{KEYCLOAK_REALM}/protocol/openid-connect/logout'

# Redirect URI after logging out from Keycloak
redirect_uri = 'http://pechchan-mvp-pehchan-portal-1:5002' # Redirect back to home after logout

# Construct the logout URL with post logout redirect URI and client ID
logout_url = f'{keycloak_logout_url}?post_logout_redirect_uri={redirect_uri}&client_id={KEYCLOAK_CLIENT_ID}'

# Clear session or tokens in Flask
session.pop('user_id', None)
return redirect(url_for('login'))
session.pop('access_token', None)
session.pop('refresh_token', None)
session.pop('id_token', None)

# Redirect to Keycloak logout URL
return redirect(logout_url)



@app.route('/upload_avatar', methods=['POST'])
Expand Down
57 changes: 53 additions & 4 deletions PehchanPortal/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,58 @@

{% block content %}
<div class="dashboard-container">
<h1>Login Successful</h1>
<p>Authorization Code: {{ code }}</p>
<a href="/">Go back to Home</a>
<h1>Welcome, {{ user.full_name }}</h1>

<!-- Avatar Management -->
<h2>Manage Your Avatar</h2>
<div class="avatar-section">
<!-- Display Current Avatar -->
<img src="{{ url_for('static', filename='avatars/' + user.avatar) }}" alt="User Avatar" class="avatar-image">

<!-- Form to Upload a New Avatar -->
<form action="/upload_avatar" method="POST" enctype="multipart/form-data">
<label for="avatar">Upload New Avatar:</label>
<input type="file" name="avatar" accept=".jpg, .jpeg, .png, .gif">
<button type="submit">Upload Avatar</button>
</form>

<!-- Remove Avatar (Reset to Default) -->
<form action="/remove_avatar" method="POST">
<button type="submit">Remove Avatar (Reset to Default)</button>
</form>
</div>

<!-- Profile Management -->
<h2>Update Your Profile</h2>
<form action="/update_profile" method="POST">
<label for="full_name">Full Name:</label>
<input type="text" name="full_name" value="{{ user.full_name }}" required><br>

<label for="email">Email:</label>
<input type="email" name="email" value="{{ user.email }}" required><br>

<label for="phone">Phone Number:</label>
<input type="tel" name="phone" value="{{ user.phone }}" required><br>

<label for="cnic">CNIC:</label>
<input type="text" name="cnic" value="{{ user.cnic }}" required><br>

<label for="gender">Gender:</label>
<input type="text" name="gender" value="{{ user.gender }}"><br>

<label for="mothers_name">Mother's Name:</label>
<input type="text" name="mothers_name" value="{{ user.mothers_name }}"><br>

<label for="address">Address:</label>
<input type="text" name="address" value="{{ user.address }}"><br>

<label for="date_of_birth">Date of Birth:</label>
<input type="date" name="date_of_birth" value="{{ user.date_of_birth }}"><br>

<button type="submit">Update Profile</button>
</form>

<!-- Logout Link -->
<a href="/logout">Logout</a>
</div>
{% endblock %}
a

0 comments on commit cb3b0e1

Please sign in to comment.