Init
This commit is contained in:
commit
9919e5d5ad
21
LICENSE
Executable file
21
LICENSE
Executable file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
5
MANIFEST.in
Executable file
5
MANIFEST.in
Executable file
@ -0,0 +1,5 @@
|
||||
include README.md
|
||||
include LICENSE
|
||||
recursive-include django_hopid/static *
|
||||
recursive-include django_hopid/templates *
|
||||
recursive-include django_hopid/templatetags *.py
|
184
README.md
Executable file
184
README.md
Executable file
@ -0,0 +1,184 @@
|
||||
# HopID Client for Django
|
||||
|
||||
**django-hopid** is a lightweight Django library that simplifies OpenID Connect (OIDC) authentication with an authorization server powered by `django-oauth-toolkit`. It offers decorators, utility functions, URLs, and template tags to streamline secure login integration.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
* [✅ Features](#✅-features)
|
||||
* [🛠️ Installation](#🛠️-installation)
|
||||
* [🚀 Quick Start](#🚀-quick-start)
|
||||
|
||||
* [1. Configure settings](#1-configure-settings)
|
||||
* [2. Include URLs](#2-include-urls)
|
||||
* [3. Use the callback decorator](#3-use-the-callback-decorator)
|
||||
* [4. Use the login button tag](#4-use-the-login-button-tag)
|
||||
* [⚙️ Advanced Usage](#⚙️-advanced-usage)
|
||||
|
||||
* [Custom callback response handling](#custom-callback-response-handling)
|
||||
* [Use login URL in views](#use-login-url-in-views)
|
||||
* [Logout integration](#logout-integration)
|
||||
* [📄 License](#📄-license)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Features
|
||||
|
||||
* 🧷 OpenID Connect Authorization Code flow with PKCE and nonce support
|
||||
* 🔐 Secure token exchange and ID token verification (including issuer, nonce, and access token hash checks)
|
||||
* 🤝 Seamless user login or creation using the access token
|
||||
* 🌈 Template tag for styled login button
|
||||
* ⚖️ Built-in URL routes for callback and logout
|
||||
* ✨ Minimal configuration, no third-party dependencies
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
```bash
|
||||
pip install django-hopid
|
||||
```
|
||||
|
||||
Add to `INSTALLED_APPS` (for templates):
|
||||
|
||||
```python
|
||||
INSTALLED_APPS = [
|
||||
...,
|
||||
'django_hopid',
|
||||
]
|
||||
```
|
||||
|
||||
Ensure `request` is in template context:
|
||||
|
||||
```python
|
||||
TEMPLATES = [
|
||||
{
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
...,
|
||||
'django.template.context_processors.request',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Configure settings
|
||||
|
||||
Add the following to your `settings.py`:
|
||||
|
||||
```python
|
||||
HOPID_URL = "https://auth.example.com"
|
||||
HOPID_CLIENT_ID = "your-client-id"
|
||||
HOPID_CLIENT_SECRET = "your-client-secret"
|
||||
HOPID_CLIENT_URI = "https://yourapp.example.com"
|
||||
```
|
||||
|
||||
### 2. Include URLs
|
||||
|
||||
In your app's `urls.py`, include HopID routes:
|
||||
|
||||
```python
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path("auth/", include("django_hopid.urls")),
|
||||
]
|
||||
```
|
||||
|
||||
This provides:
|
||||
|
||||
* `/auth/callback/` for OIDC callback
|
||||
* `/auth/logout/` for logout
|
||||
|
||||
### 3. Use the callback decorator
|
||||
|
||||
Create your callback view:
|
||||
|
||||
```python
|
||||
from django.shortcuts import render
|
||||
from django_hopid.decorators import hopid_callback
|
||||
|
||||
@hopid_callback()
|
||||
def hopid_callback_view(request, *args, **kwargs):
|
||||
user = kwargs.get("user")
|
||||
return render(request, "home.html", {"user": user})
|
||||
```
|
||||
|
||||
### 4. Use the login button tag
|
||||
|
||||
In any template:
|
||||
|
||||
```django
|
||||
{% load hopid_tags %}
|
||||
{% hopid_login_button %}
|
||||
```
|
||||
|
||||
This renders a styled login button based on your template:
|
||||
|
||||
```html
|
||||
<a href="https://auth.example.com/o/authorize/?..." class="hopid-login-button">
|
||||
Login with HopID
|
||||
</a>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Advanced Usage
|
||||
|
||||
### Custom callback response handling
|
||||
|
||||
Pass a custom `response=` to the decorator to override error handling:
|
||||
|
||||
```python
|
||||
@hopid_callback(response=my_error_view)
|
||||
def hopid_callback_view(request, user=None, error=None):
|
||||
if error:
|
||||
return render(request, "error.html", {"error": error})
|
||||
return render(request, "home.html", {"user": user})
|
||||
```
|
||||
|
||||
### Use login URL in views
|
||||
|
||||
If you want to build your own login redirect view:
|
||||
|
||||
```python
|
||||
from django.shortcuts import redirect
|
||||
from django_hopid.utils import get_hopid_login_url
|
||||
|
||||
def login_view(request):
|
||||
return redirect(get_hopid_login_url(request))
|
||||
```
|
||||
|
||||
### Logout integration
|
||||
|
||||
Logout is handled via:
|
||||
|
||||
```python
|
||||
from django.contrib.auth import logout as django_logout
|
||||
from django.shortcuts import redirect
|
||||
from django_hopid.utils import get_hopid_logout_url
|
||||
|
||||
def logout_view(request):
|
||||
django_logout(request)
|
||||
return redirect(get_hopid_logout_url(request))
|
||||
```
|
||||
|
||||
Or use the built-in route `/auth/logout/`.
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Open issues or pull requests on [Git](https://git.hopsenn.com/hopsenn/django-hopid).
|
23
django_hopid/__init__.py
Executable file
23
django_hopid/__init__.py
Executable file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
76
django_hopid/decorators.py
Executable file
76
django_hopid/decorators.py
Executable file
@ -0,0 +1,76 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
from django.contrib.auth import login as auth_login
|
||||
|
||||
from . import settings
|
||||
from .validators import verify_id_token
|
||||
from .utils import get_jwt_tokens, get_user_from_token
|
||||
|
||||
|
||||
def hopid_callback(response=None):
|
||||
def decorator(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
def fail(reason):
|
||||
if callable(response):
|
||||
return response(request, reason)
|
||||
|
||||
return view_func(request, *args, **kwargs, error=reason)
|
||||
|
||||
code = request.GET.get('code')
|
||||
if not code:
|
||||
return fail("No code returned")
|
||||
|
||||
tokens = get_jwt_tokens(code, request.session.pop('pkce_verifier', ''))
|
||||
error = tokens.get('error', '')
|
||||
|
||||
if error:
|
||||
return fail(error)
|
||||
|
||||
access_token = tokens.get('access_token')
|
||||
id_token = tokens.get('id_token')
|
||||
|
||||
if not access_token or not id_token:
|
||||
return fail("No ID token returned")
|
||||
|
||||
claims = verify_id_token(id_token, access_token)
|
||||
if not claims:
|
||||
return fail("Invalid ID token")
|
||||
|
||||
expected_nonce = request.session.pop('oidc_nonce', None)
|
||||
|
||||
if not claims or claims.get("nonce") != expected_nonce:
|
||||
return fail("Invalid or missing nonce")
|
||||
|
||||
profile = get_user_from_token(access_token)
|
||||
if not profile:
|
||||
return fail("User info request failed")
|
||||
|
||||
auth_login(request, profile)
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return _wrapped_view
|
||||
return decorator
|
35
django_hopid/settings.py
Executable file
35
django_hopid/settings.py
Executable file
@ -0,0 +1,35 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
def get(key, default=None):
|
||||
return getattr(settings, key, default)
|
||||
|
||||
HOPID_URL = get('HOPID_URL', 'https://id.hopsenn.com')
|
||||
HOPID_CLIENT_ID = get('HOPID_CLIENT_ID', '')
|
||||
HOPID_CLIENT_SECRET = get('HOPID_CLIENT_SECRET', '')
|
||||
HOPID_CLIENT_URI = get('HOPID_CLIENT_URI', '')
|
||||
HOPID_CLIENT_LOGOUT_URI = get('HOPID_CLIENT_LOGOUT_URI', '')
|
||||
HOPID_CLIENT_CALLBACK_URI = get('HOPID_CLIENT_CALLBACK_URI', '')
|
@ -0,0 +1,3 @@
|
||||
<a href="{{ url }}" class="hopid-login-button" style="display:inline-block;padding:10px 20px;border-radius:8px;background:#1e88e5;color:white;text-decoration:none;font-weight:500;">
|
||||
Login with HopID
|
||||
</a>
|
23
django_hopid/templatetags/__init__.py
Normal file
23
django_hopid/templatetags/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
43
django_hopid/templatetags/hopid_tags.py
Normal file
43
django_hopid/templatetags/hopid_tags.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django_hopid.utils import get_hopid_login_url
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def hopid_login_url(context):
|
||||
request = context['request']
|
||||
return get_hopid_login_url(request)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def hopid_login_button(context):
|
||||
request = context['request']
|
||||
url = get_hopid_login_url(request)
|
||||
|
||||
return render_to_string('django_hopid/hopid_login_button.html', {'url': url})
|
9
django_hopid/urls.py
Normal file
9
django_hopid/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
from django_hopid.views import hopid_logout_view, hopid_callback_view
|
||||
|
||||
app_name = "django_hopid"
|
||||
|
||||
urlpatterns = [
|
||||
path("callback/", hopid_callback_view, name="callback"),
|
||||
path("logout/", hopid_logout_view, name="logout"),
|
||||
]
|
113
django_hopid/utils.py
Normal file
113
django_hopid/utils.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import base64
|
||||
import secrets
|
||||
import requests
|
||||
|
||||
from . import settings
|
||||
from urllib.parse import urlencode
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
def get_hopid_logout_url():
|
||||
next_url = settings.HOPID_CLIENT_LOGOUT_URI or settings.HOPID_CLIENT_URI
|
||||
query = urlencode({"next": next_url})
|
||||
return f"{settings.HOPID_URL}/users/logout/?{query}"
|
||||
|
||||
|
||||
def get_user_from_token(access_token):
|
||||
userinfo_url = f"{settings.HOPID_URL}/users/userinfo/"
|
||||
headers = {'Authorization': f'Bearer {access_token}'}
|
||||
|
||||
try:
|
||||
userinfo_response = requests.get(userinfo_url, headers=headers, timeout=5)
|
||||
except requests.RequestException:
|
||||
return None
|
||||
|
||||
if userinfo_response.status_code != 200:
|
||||
return None
|
||||
|
||||
userinfo = userinfo_response.json()
|
||||
email = userinfo.get('email')
|
||||
username = userinfo.get('username')
|
||||
|
||||
if not email or not username:
|
||||
return None
|
||||
|
||||
profile, _ = get_user_model().objects.get_or_create(email=email, defaults={'username': username.lower()})
|
||||
return profile
|
||||
|
||||
|
||||
def get_jwt_tokens(code, code_verifier):
|
||||
token_url = f"{settings.HOPID_URL}/o/token/"
|
||||
token_data = {
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'redirect_uri': settings.HOPID_CLIENT_CALLBACK_URI or settings.HOPID_CLIENT_URI + '/id/callback/',
|
||||
'client_id': settings.HOPID_CLIENT_ID,
|
||||
'client_secret': settings.HOPID_CLIENT_SECRET,
|
||||
'code_verifier': code_verifier,
|
||||
}
|
||||
|
||||
try:
|
||||
token_response = requests.post(token_url, data=token_data, timeout=5)
|
||||
except requests.RequestException:
|
||||
return {"error": "Token request failed"}
|
||||
|
||||
if token_response.status_code != 200:
|
||||
return {"error": "Token exchange failed"}
|
||||
|
||||
tokens = token_response.json()
|
||||
return tokens
|
||||
|
||||
|
||||
def generate_pkce_pair():
|
||||
verifier = secrets.token_urlsafe(64)
|
||||
challenge = base64.urlsafe_b64encode(
|
||||
hashlib.sha256(verifier.encode()).digest()
|
||||
).rstrip(b'=').decode()
|
||||
return verifier, challenge
|
||||
|
||||
|
||||
def get_hopid_login_url(request):
|
||||
nonce = secrets.token_urlsafe(32)
|
||||
verifier, challenge = generate_pkce_pair()
|
||||
|
||||
request.session['pkce_verifier'] = verifier
|
||||
request.session['oidc_nonce'] = nonce
|
||||
|
||||
base = f"{settings.HOPID_URL}/o/authorize/"
|
||||
|
||||
params = {
|
||||
"client_id": settings.HOPID_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"scope": "openid profile email",
|
||||
"redirect_uri": settings.HOPID_CLIENT_CALLBACK_URI or settings.HOPID_CLIENT_URI + '/id/callback/',
|
||||
"nonce": nonce,
|
||||
"code_challenge": challenge,
|
||||
"code_challenge_method": "S256"
|
||||
}
|
||||
return f"{base}?{urlencode(params)}"
|
56
django_hopid/validators.py
Normal file
56
django_hopid/validators.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import requests
|
||||
|
||||
from jose import jwt, JWTError
|
||||
from . import settings
|
||||
|
||||
|
||||
def verify_id_token(id_token, access_token):
|
||||
jwks = requests.get(f"{settings.HOPID_URL}/.well-known/jwks.json").json()
|
||||
|
||||
try:
|
||||
# Get unverified header to find 'kid'
|
||||
unverified_header = jwt.get_unverified_header(id_token)
|
||||
kid = unverified_header['kid']
|
||||
|
||||
# Find the matching key
|
||||
key = next(k for k in jwks['keys'] if k['kid'] == kid)
|
||||
issuer = settings.HOPID_URL.rstrip('/')
|
||||
|
||||
# Decode & verify
|
||||
claims = jwt.decode(
|
||||
id_token,
|
||||
key=key,
|
||||
algorithms=['RS256'],
|
||||
audience=settings.HOPID_CLIENT_ID,
|
||||
issuer=issuer,
|
||||
access_token=access_token
|
||||
)
|
||||
|
||||
return claims # Validated claims
|
||||
|
||||
except JWTError as e:
|
||||
print("Invalid id_token:", e)
|
13
django_hopid/views.py
Normal file
13
django_hopid/views.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.auth import logout as django_logout
|
||||
|
||||
from .utils import get_hopid_logout_url
|
||||
from .decorators import hopid_callback
|
||||
|
||||
@hopid_callback()
|
||||
def hopid_callback_view(request, *args, **kwargs):
|
||||
return redirect("/")
|
||||
|
||||
def hopid_logout_view(request):
|
||||
django_logout(request)
|
||||
return redirect(get_hopid_logout_url())
|
23
example/demoapp/demoapp/__init__.py
Executable file
23
example/demoapp/demoapp/__init__.py
Executable file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
31
example/demoapp/demoapp/asgi.py
Executable file
31
example/demoapp/demoapp/asgi.py
Executable file
@ -0,0 +1,31 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demoapp.settings')
|
||||
|
||||
application = get_asgi_application()
|
141
example/demoapp/demoapp/settings.py
Executable file
141
example/demoapp/demoapp/settings.py
Executable file
@ -0,0 +1,141 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-jv__th8%4!0ite+vhv%c@(q$1=n8xwg(46@k3zbz7^oeq-+)p&'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_hopid'
|
||||
]
|
||||
|
||||
HOPID_URL = 'http://localhost:8000'
|
||||
HOPID_CLIENT_ID = 'XUFHPoHC9bKF8Vf93srxqfmyjtXCSwx3KsQdsNDP'
|
||||
HOPID_CLIENT_SECRET = 'ipzAGxVsdh9nt5rmYXHcNpkIw8XzYOd6WyVApWraMD0ehclLzN7F94kEcUYIweSVsrWgWMOu0Qp8oqqWUpPIhqOxTyIH6qFmaE14KBwWSInx1H3tlVQWLdrSpjmJhEru'
|
||||
HOPID_CLIENT_URI = 'http://localhost:8001'
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'demoapp.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'demoapp'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'demoapp.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
63
example/demoapp/demoapp/templates/index.html
Executable file
63
example/demoapp/demoapp/templates/index.html
Executable file
@ -0,0 +1,63 @@
|
||||
{% load static hopid_tags %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>HopID Demo</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
h2 { color: #333; }
|
||||
nav {
|
||||
background-color: #222;
|
||||
padding: 10px;
|
||||
}
|
||||
nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin: 0 10px;
|
||||
}
|
||||
form {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
max-width: 400px;
|
||||
}
|
||||
.my-button, input[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
.my-button:hover, input[type="submit"]:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
input[type="text"], input[type="password"], input[type="date"], select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin: 6px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>HopID Demo</h1>
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<p>Hello, {{ request.user.username }}!</p>
|
||||
<a href="/id/logout" class="my-button">Logout</a>
|
||||
{% else %}
|
||||
<a href="{% hopid_login_url %}" class="my-button">Login with HopID</a>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
39
example/demoapp/demoapp/urls.py
Executable file
39
example/demoapp/demoapp/urls.py
Executable file
@ -0,0 +1,39 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', views.index, name='index'),
|
||||
path("id/", include("django_hopid.urls")),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
28
example/demoapp/demoapp/views.py
Executable file
28
example/demoapp/demoapp/views.py
Executable file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
def index(request):
|
||||
return render(request, "templates/index.html")
|
31
example/demoapp/demoapp/wsgi.py
Executable file
31
example/demoapp/demoapp/wsgi.py
Executable file
@ -0,0 +1,31 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demoapp.settings')
|
||||
|
||||
application = get_wsgi_application()
|
44
example/demoapp/manage.py
Executable file
44
example/demoapp/manage.py
Executable file
@ -0,0 +1,44 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demoapp.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
51
setup.py
Executable file
51
setup.py
Executable file
@ -0,0 +1,51 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='django-hopid',
|
||||
version='1.0.0',
|
||||
description='',
|
||||
author='Hopsenn',
|
||||
author_email='ivan.nikolskiy@hopsenn.com',
|
||||
url='https://git.hopsenn.com/hopsenn/django-hopid',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'requests>=2.20',
|
||||
'Django>=3.2',
|
||||
'python-jose',
|
||||
'requests'
|
||||
],
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3',
|
||||
],
|
||||
python_requires='>=3.6',
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user