Skip to content

Commit

Permalink
Merge pull request #22 from OVINC-CN/feat_app_auth
Browse files Browse the repository at this point in the history
feat(auth): use sha256 auth instead of plain text
  • Loading branch information
OrenZhang authored Dec 18, 2024
2 parents 42760e3 + d241ce4 commit 9143158
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .cruft.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"template": "https://github.com/OVINC-CN/DevTemplateDjango.git",
"commit": "5281a1802f609dde876e1b80816a82c86a202767",
"commit": "9a6da21eb67f025defc444bb99d6cd48142d2aad",
"checkout": "main",
"context": {
"cookiecutter": {
Expand Down
18 changes: 18 additions & 0 deletions apps/application/migrations/0004_alter_application_app_secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pylint: disable=C0103
# Generated by Django 4.2.17 on 2024-12-18 07:18

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("application", "0003_application_is_hidden"),
]

operations = [
migrations.AlterField(
model_name="application",
name="app_secret",
field=models.CharField(blank=True, max_length=255, null=True, verbose_name="App Secret"),
),
]
41 changes: 29 additions & 12 deletions apps/application/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import hashlib
import secrets
import time
from typing import List

from django.conf import settings
from django.contrib.auth.hashers import check_password, make_password
from django.core.cache import cache
from django.db import models, transaction
from django.utils.translation import gettext_lazy
from ovinc_client.core.constants import SHORT_CHAR_LENGTH
from django_redis.client import DefaultClient
from ovinc_client.core.constants import MAX_CHAR_LENGTH, SHORT_CHAR_LENGTH
from ovinc_client.core.models import (
BaseModel,
ForeignKey,
Expand All @@ -14,6 +18,9 @@
)

from apps.account.models import User
from core.constants import APP_AUTH_NONCE_CACHE_KEY

cache: DefaultClient


class ApplicationObjects(SoftDeletedManager):
Expand Down Expand Up @@ -47,7 +54,7 @@ class Application(SoftDeletedModel):

app_name = models.CharField(gettext_lazy("App Name"), max_length=SHORT_CHAR_LENGTH)
app_code = models.CharField(gettext_lazy("App Code"), max_length=SHORT_CHAR_LENGTH, primary_key=True)
app_secret = models.TextField(gettext_lazy("App Secret (Encoded)"), blank=True, null=True)
app_secret = models.CharField(gettext_lazy("App Secret"), max_length=MAX_CHAR_LENGTH, blank=True, null=True)
app_url = models.URLField(gettext_lazy("App Url"), null=True, blank=True)
app_logo = models.URLField(gettext_lazy("App Logo"), null=True, blank=True)
app_desc = models.TextField(gettext_lazy("App Desc"), null=True, blank=True)
Expand All @@ -60,24 +67,34 @@ class Meta:
verbose_name_plural = verbose_name
ordering = ["app_code"]

def check_secret(self, raw_secret: str) -> bool:
def check_sign(self, signature: str, timestamp: str, nonce: str) -> bool:
"""
check secret
check signature
"""

if settings.ENCRYPT_APP_SECRET:
return check_password(raw_secret, self.app_secret)
return raw_secret == self.app_secret
# check timestamp
if not timestamp.isdigit() or int(timestamp) + settings.APP_AUTH_SIGN_EXPIRE < time.time():
return False

# check nonce deplicate
if not cache.add(
key=APP_AUTH_NONCE_CACHE_KEY.format(appid=self.app_code, nonce=nonce),
value=signature,
timeout=settings.APP_AUTH_SIGN_EXPIRE,
):
return False

# check signature
raw_content = f"{timestamp}-{nonce}-{self.app_secret}"
expect_signature = hashlib.sha256(raw_content.encode()).hexdigest()
return secrets.compare_digest(signature, expect_signature)

def set_secret(self, raw_secret: str) -> None:
"""
set secret
"""

if settings.ENCRYPT_APP_SECRET:
self.app_secret = make_password(raw_secret)
else:
self.app_secret = raw_secret
self.app_secret = raw_secret
self.save(update_fields=["app_secret"])


Expand Down
36 changes: 17 additions & 19 deletions core/auth.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import json

from channels.db import database_sync_to_async
from django.contrib.auth import get_user_model
from django.utils.translation import gettext
from ovinc_client.constants import APP_AUTH_ID_KEY, APP_AUTH_SECRET_KEY
from rest_framework.authentication import BaseAuthentication
from rest_framework.request import Request

from apps.application.models import Application
from core.constants import APP_AUTH_HEADER_KEY
from core.constants import (
APP_AUTH_HEADER_APPID_KEY,
APP_AUTH_HEADER_APPID_NONCE,
APP_AUTH_HEADER_APPID_SIGN,
APP_AUTH_HEADER_APPID_TIMESTAMP,
)
from core.exceptions import AppAuthFailed

USER_MODEL = get_user_model()
Expand All @@ -19,25 +22,20 @@ class ApplicationAuthenticate(BaseAuthentication):
"""

# pylint: disable=W0236
async def authenticate(self, request) -> (Application, None):
# get from query
if APP_AUTH_ID_KEY in request.query_params:
app_params = request.query_params
# get app params
else:
app_params = json.loads(request.META.get(APP_AUTH_HEADER_KEY, "{}"))
if not isinstance(app_params, dict):
raise AppAuthFailed(gettext("App Auth Params is not Json"))
app_code = app_params.get(APP_AUTH_ID_KEY)
app_secret = app_params.get(APP_AUTH_SECRET_KEY)
if not app_code or not app_secret:
raise AppAuthFailed(gettext("App Auth Params Not Exist"))
async def authenticate(self, request: Request) -> (Application, None):
# load params
app_code = request.headers.get(APP_AUTH_HEADER_APPID_KEY)
signature = request.headers.get(APP_AUTH_HEADER_APPID_SIGN)
timestamp = request.headers.get(APP_AUTH_HEADER_APPID_TIMESTAMP)
nonce = request.headers.get(APP_AUTH_HEADER_APPID_NONCE)
if not app_code or not signature or not timestamp or not nonce:
raise AppAuthFailed(gettext("App Auth Headers Not Exist"))
# varify app
try:
app = await database_sync_to_async(Application.objects.get)(pk=app_code)
except Application.DoesNotExist as err: # pylint: disable=E1101
raise AppAuthFailed(gettext("App Not Exist")) from err
# verify secret
if app.check_secret(app_secret):
if app.check_sign(signature=signature, timestamp=timestamp, nonce=nonce):
return app, None
raise AppAuthFailed(gettext("App Code or Secret Incorrect"))
raise AppAuthFailed(gettext("Signature Invalid"))
8 changes: 7 additions & 1 deletion core/constants.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
APP_AUTH_HEADER_KEY = "HTTP_OVINC_APP"
APP_AUTH_HEADER_APPID_KEY = "ovinc-appid"
APP_AUTH_HEADER_APPID_SIGN = "ovinc-sign"
APP_AUTH_HEADER_APPID_TIMESTAMP = "ovinc-timestamp"
APP_AUTH_HEADER_APPID_NONCE = "ovinc-nonce"

APP_AUTH_NONCE_CACHE_KEY = "ovinc-app-auth:nonce:{appid}:{nonce}"

WECHAT_USER_AGENT_KEY = "micromessenger"
3 changes: 1 addition & 2 deletions entry/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@
AUTH_USER_MODEL = "account.User"

# App
# enable will spend extra time at app request
ENCRYPT_APP_SECRET = strtobool(os.getenv("ENCRYPT_APP_SECRET", "False"))
APP_AUTH_SIGN_EXPIRE = int(os.getenv("APP_AUTH_SIGN_EXPIRE", "60"))

# Celery
CELERY_TIMEZONE = TIME_ZONE
Expand Down
15 changes: 6 additions & 9 deletions locale/zh_Hans/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-29 21:14+0800\n"
"POT-Creation-Date: 2024-12-18 15:18+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -176,8 +176,8 @@ msgstr "应用名称"
msgid "App Code"
msgstr "应用ID"

msgid "App Secret (Encoded)"
msgstr "应用密码(加密)"
msgid "App Secret"
msgstr "应用密码"

msgid "App Url"
msgstr "应用访问地址"
Expand Down Expand Up @@ -352,17 +352,14 @@ msgstr ""
"审核ID: %(audit_id)s\n"
"审核时间: %(creation_time)s\n"

msgid "App Auth Params is not Json"
msgstr "应用鉴权参数不是JSON"

msgid "App Auth Params Not Exist"
msgid "App Auth Headers Not Exist"
msgstr "应用鉴权参数不存在"

msgid "App Not Exist"
msgstr "应用不存在"

msgid "App Code or Secret Incorrect"
msgstr "应用ID或者密钥不正确"
msgid "Signature Invalid"
msgstr "签名非法"

msgid "Auth Token Invalid"
msgstr "认证 Token 非法"
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ovinc
ovinc-client==0.3.14
ovinc-client==0.3.15

# Celery
celery==5.4.0
Expand Down

0 comments on commit 9143158

Please sign in to comment.