Implement sessions
- Missing security 💀
Signed-off-by: Tiago Garcia <tiago.rgarcia@ua.pt>
This commit is contained in:
parent
50cd873bdc
commit
a28dc261f5
|
@ -1,7 +1,7 @@
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from routes import org_bp, user_bp, file_bp
|
from routes import org_bp, user_bp, file_bp
|
||||||
from database import db_connection
|
from database import db_connection
|
||||||
from models import Organization, User, File
|
from models import Organization, User, File, Session
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
|
||||||
|
@ -16,5 +16,12 @@ app.register_blueprint(org_bp, url_prefix="/org")
|
||||||
app.register_blueprint(user_bp, url_prefix="/user")
|
app.register_blueprint(user_bp, url_prefix="/user")
|
||||||
app.register_blueprint(file_bp, url_prefix="/file")
|
app.register_blueprint(file_bp, url_prefix="/file")
|
||||||
|
|
||||||
|
@app.route("/reset")
|
||||||
|
def reset():
|
||||||
|
with app.app_context():
|
||||||
|
db_connection.drop_all()
|
||||||
|
db_connection.create_all()
|
||||||
|
return "Database reset"
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
|
@ -1,3 +1,4 @@
|
||||||
from .user import *
|
from .user import *
|
||||||
from .org import *
|
from .org import *
|
||||||
from .file import *
|
from .file import *
|
||||||
|
from .session import *
|
|
@ -0,0 +1,21 @@
|
||||||
|
from database import db_connection
|
||||||
|
|
||||||
|
class Session(db_connection.Model):
|
||||||
|
__tablename__ = 'sessions'
|
||||||
|
|
||||||
|
id = db_connection.Column(db_connection.Integer, primary_key=True)
|
||||||
|
user_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'))
|
||||||
|
org_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('organizations.id'))
|
||||||
|
token = db_connection.Column(db_connection.String(255), unique=True)
|
||||||
|
created_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now())
|
||||||
|
updated_at = db_connection.Column(db_connection.DateTime, server_default=db_connection.func.now(), server_onupdate=db_connection.func.now())
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"user_id": self.user_id,
|
||||||
|
"org_id": self.org_id,
|
||||||
|
"token": self.token,
|
||||||
|
"created_at": self.created_at,
|
||||||
|
"updated_at": self.updated_at
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from database import db_connection
|
from database import db_connection
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ class User(db_connection.Model):
|
||||||
username = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
username = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
||||||
full_name = db_connection.Column(db_connection.String, nullable=False)
|
full_name = db_connection.Column(db_connection.String, nullable=False)
|
||||||
email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False)
|
||||||
public_key = db_connection.Column(db_connection.String, nullable=False)
|
public_keys = db_connection.Column(db_connection.JSON, nullable=False, default=dict)
|
||||||
orgs = db_connection.relationship('Organization', back_populates='owner')
|
orgs = db_connection.relationship('Organization', back_populates='owner')
|
||||||
files = db_connection.relationship('File', back_populates='creator')
|
files = db_connection.relationship('File', back_populates='creator')
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class User(db_connection.Model):
|
||||||
"username": self.username,
|
"username": self.username,
|
||||||
"full_name": self.full_name,
|
"full_name": self.full_name,
|
||||||
"email": self.email,
|
"email": self.email,
|
||||||
"public_key": self.public_key,
|
"public_keys": [{"org_id": org_id, "key": public_key} for org_id, public_key in self.public_keys.items()],
|
||||||
"orgs": [{"id": org.id, "name": org.name} for org in self.orgs],
|
"orgs": [{"id": org.id, "name": org.name} for org in self.orgs],
|
||||||
"files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files]
|
"files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files]
|
||||||
}
|
}
|
|
@ -4,8 +4,15 @@ from services import OrganizationService
|
||||||
org_bp = Blueprint("org", __name__)
|
org_bp = Blueprint("org", __name__)
|
||||||
|
|
||||||
@org_bp.route("/create", methods=["POST"])
|
@org_bp.route("/create", methods=["POST"])
|
||||||
def create():
|
def org_create():
|
||||||
data = request.json
|
data = request.json
|
||||||
|
if "name" not in data or "username" not in data or "full_name" not in data or "email" not in data or "public_key" not in data:
|
||||||
|
return jsonify({"error": "Missing required fields"}), 400
|
||||||
|
|
||||||
|
existing_org = OrganizationService.get_organization_by_name(data["name"])
|
||||||
|
if existing_org:
|
||||||
|
return jsonify({"error": "Organization already exists"}), 400
|
||||||
|
|
||||||
org = OrganizationService.create_organization(
|
org = OrganizationService.create_organization(
|
||||||
name=data["name"],
|
name=data["name"],
|
||||||
username=data["username"],
|
username=data["username"],
|
||||||
|
@ -13,4 +20,10 @@ def create():
|
||||||
email=data["email"],
|
email=data["email"],
|
||||||
public_key=data["public_key"]
|
public_key=data["public_key"]
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonify(org.to_dict()), 201
|
return jsonify(org.to_dict()), 201
|
||||||
|
|
||||||
|
@org_bp.route("/list", methods=["GET"])
|
||||||
|
def org_list():
|
||||||
|
orgs = OrganizationService.list_organizations()
|
||||||
|
return jsonify([org.to_dict() for org in orgs])
|
||||||
|
|
|
@ -1,4 +1,25 @@
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from services import UserService
|
from services import UserService, SessionService, OrganizationService
|
||||||
|
|
||||||
user_bp = Blueprint("user", __name__)
|
user_bp = Blueprint("user", __name__)
|
||||||
|
|
||||||
|
@user_bp.route("/login", methods=["POST"])
|
||||||
|
def user_login():
|
||||||
|
data = request.json
|
||||||
|
user = UserService.get_user_by_username(data["username"])
|
||||||
|
if not user:
|
||||||
|
return jsonify({"error": "User not found"}), 404
|
||||||
|
|
||||||
|
org = OrganizationService.get_organization_by_name(data["org"])
|
||||||
|
if not org:
|
||||||
|
return jsonify({"error": "Organization not found"}), 404
|
||||||
|
|
||||||
|
id_str = str(org.id)
|
||||||
|
if id_str not in user.public_keys:
|
||||||
|
return jsonify({"error": "User not associated with organization"}), 403
|
||||||
|
|
||||||
|
if user.public_keys[id_str] != data["public_key"]:
|
||||||
|
return jsonify({"error": "Invalid public key"}), 403
|
||||||
|
|
||||||
|
session = SessionService.create_session(user, org)
|
||||||
|
return jsonify(session.to_dict()), 201
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
from .orgs import OrganizationService
|
from .orgs import OrganizationService
|
||||||
from .users import UserService
|
from .users import UserService
|
||||||
from .files import FileService
|
from .files import FileService
|
||||||
|
from .sessions import SessionService
|
|
@ -28,6 +28,7 @@ class OrganizationService:
|
||||||
db.refresh(organization)
|
db.refresh(organization)
|
||||||
|
|
||||||
UserService().add_org_to_user(user, organization)
|
UserService().add_org_to_user(user, organization)
|
||||||
|
UserService().add_public_key_to_user(user, organization, public_key)
|
||||||
|
|
||||||
return organization
|
return organization
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import secrets
|
||||||
|
from database import db
|
||||||
|
from models import Session, User, Organization
|
||||||
|
|
||||||
|
|
||||||
|
class SessionService:
|
||||||
|
@staticmethod
|
||||||
|
def create_session(user: User, org: Organization) -> Session:
|
||||||
|
session = Session(
|
||||||
|
user_id=user.id,
|
||||||
|
org_id=org.id,
|
||||||
|
token=secrets.token_hex(128)
|
||||||
|
)
|
||||||
|
db.add(session)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(session)
|
||||||
|
return session
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_session_by_token(token: str) -> Session | None:
|
||||||
|
return db.query(Session).filter(Session.token == token).first()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_session(session: Session) -> None:
|
||||||
|
db.delete(session)
|
||||||
|
db.commit()
|
|
@ -9,7 +9,7 @@ class UserService:
|
||||||
username=username,
|
username=username,
|
||||||
full_name=full_name,
|
full_name=full_name,
|
||||||
email=email,
|
email=email,
|
||||||
public_key=public_key,
|
public_keys={org.id: public_key} if org else {},
|
||||||
orgs=[org] if org else []
|
orgs=[org] if org else []
|
||||||
)
|
)
|
||||||
db.add(user)
|
db.add(user)
|
||||||
|
@ -30,4 +30,13 @@ class UserService:
|
||||||
user.orgs.append(org)
|
user.orgs.append(org)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_public_key_to_user(user: User, org: Organization, public_key: str) -> User:
|
||||||
|
public_keys = user.public_keys.copy()
|
||||||
|
public_keys[org.id] = public_key
|
||||||
|
user.public_keys = public_keys
|
||||||
|
db.commit()
|
||||||
|
db.refresh(user)
|
||||||
return user
|
return user
|
Loading…
Reference in New Issue