Skip to content

Commit

Permalink
Merge pull request #19 from t3tra-dev/feat/oauth
Browse files Browse the repository at this point in the history
add OAuthManager
  • Loading branch information
t3tra-dev authored Nov 7, 2024
2 parents 833768b + 1de0a2e commit 94f0397
Show file tree
Hide file tree
Showing 9 changed files with 516 additions and 10 deletions.
2 changes: 1 addition & 1 deletion haru/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .auth_manager import AuthManager
from .manager import AuthManager
from .mixins import UserMixin

__all__ = ["AuthManager", "UserMixin"]
File renamed without changes.
4 changes: 4 additions & 0 deletions haru/auth/mixins.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
This module defines models and mixins for the OAuth 2.0 implementation.
"""

from typing import Protocol

__all__ = ["UserMixin"]
Expand Down
4 changes: 4 additions & 0 deletions haru/oauth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .manager import OAuthManager
from .mixins import UserMixin

__all__ = ['OAuthManager', 'UserMixin']
380 changes: 380 additions & 0 deletions haru/oauth/manager.py

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions haru/oauth/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
This module defines models and mixins for the OAuth 2.0 implementation.
"""

from typing import Protocol

__all__ = ["UserMixin"]


class UserMixin(Protocol):
"""
Provides default implementations for methods that a user class should have.
"""

def get_id(self) -> str:
"""
Return the unique identifier of the user as a string.
"""
raise NotImplementedError(
"Method 'get_id' must be implemented to return a unique identifier for the user"
)

@property
def is_authenticated(self) -> bool:
"""
Return True if the user is authenticated.
"""
return True
23 changes: 14 additions & 9 deletions haru/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ def __init__(
headers: Dict[str, str],
body: bytes = b"",
client_address: Optional[str] = None,
app: 'Haru' = None
app: Haru = None
):
self.app: 'Haru' = app
self.app: Haru = app
self.method: str = method
self.path: str = path
self.headers: Dict[str, str] = headers
Expand All @@ -55,6 +55,7 @@ def __init__(
self.form: Dict[str, Any] = self._parse_form_data()
self.json: Optional[Dict[str, Any]] = self._parse_json()
self.files: Dict[str, Any] = {}
self._current_user: Optional[Any] = None

def _parse_cookies(self) -> Dict[str, str]:
"""
Expand Down Expand Up @@ -140,12 +141,14 @@ def get_body(self) -> bytes:
"""
return self.body

def login(self, user: 'UserMixin'):
def login(self, user: UserMixin):
"""
Log in the specified user.
"""
if self.app.auth_manager:
self.app.auth_manager.login(self, user)
if self.app.oauth_manager:
self.app.oauth_manager.login(self, user)

def logout(self):
"""
Expand All @@ -159,9 +162,11 @@ def current_user(self) -> Optional[Any]:
"""
Return the authenticated user if logged in, otherwise None.
"""
if not hasattr(self, '_current_user'):
if self.app.auth_manager:
self._current_user = self.app.auth_manager.load_user(self)
else:
self._current_user = None
return self._current_user
return getattr(self, '_current_user', None)

@current_user.setter
def current_user(self, user: Optional[Any]):
"""
Set the current user.
"""
self._current_user = user
70 changes: 70 additions & 0 deletions tests/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# app.py

from typing import Any, Dict, Optional
from haru import Haru, Request, Response
from haru.oauth import OAuthManager, UserMixin

app = Haru(__name__)


oauth_manager = OAuthManager(secret_key='your-secret-key')
oauth_manager.init_app(app)


class User(UserMixin):
def __init__(self, user_id: str, username: str):
self.id = user_id
self.username = username

def get_id(self) -> str:
return self.id


@oauth_manager.user_loader
def load_user(user_id: str) -> Optional[User]:
if user_id == '1':
return User(user_id='1', username='john')
return None


@oauth_manager.client_loader
def load_client(client_id: str) -> Optional[Dict[str, Any]]:
if client_id == 'client_123':
return {
'client_id': 'client_123',
'client_secret': 'secret_456',
'redirect_uris': ['http://localhost:8000/callback'],
'intents': ['read', 'write'],
}
return None


@app.route('/login', methods=['GET', 'POST'])
def login(request: Request):
if request.method == 'GET':
return Response('Login Form', content_type='text/html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username == 'john' and password == 'secret':
user = User(user_id='1', username='john')
oauth_manager.login(request, user)
return Response('Logged in')
else:
return Response('Invalid credentials', status_code=401)


@app.route('/logout')
def logout(request: Request):
oauth_manager.logout(request)
return Response('Logged out')


@app.route('/protected')
@oauth_manager.login_require(intents=['read'])
def protected_resource(request: Request):
user = request.current_user
return Response(f'Hello, {user.username}!')


app.run()
15 changes: 15 additions & 0 deletions tests/oauth.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
rm -rf build
rm -rf dist
python -m venv .build-venv
source .build-venv/bin/activate
pip install -U setuptools
python setup.py sdist bdist_wheel
rm -rf .build-venv

python3 -m venv .test-venv
source .test-venv/bin/activate
pip install -e .
clear
cd tests
python oauth.py
rm -rf .test-venv

0 comments on commit 94f0397

Please sign in to comment.