bazis-authing 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bazis/contrib/authing/__init__.py +19 -0
- bazis/contrib/authing/_version.py +34 -0
- bazis/contrib/authing/apps.py +22 -0
- bazis/contrib/authing/conf.py +38 -0
- bazis/contrib/authing/locale/en/LC_MESSAGES/django.mo +0 -0
- bazis/contrib/authing/locale/en/LC_MESSAGES/django.po +58 -0
- bazis/contrib/authing/locale/ru/LC_MESSAGES/django.mo +0 -0
- bazis/contrib/authing/locale/ru/LC_MESSAGES/django.po +78 -0
- bazis/contrib/authing/router.py +15 -0
- bazis/contrib/authing/routes.py +113 -0
- bazis/contrib/authing/schemas.py +27 -0
- bazis/contrib/authing/service.py +212 -0
- bazis/contrib/authing/services/__init__.py +14 -0
- bazis/contrib/authing/services/google/__init__.py +29 -0
- bazis/contrib/authing/services/google/conf.py +28 -0
- bazis/contrib/authing/services/google/router.py +15 -0
- bazis/contrib/authing/services/google/routes.py +156 -0
- bazis/contrib/authing/services/password/__init__.py +29 -0
- bazis/contrib/authing/services/password/router.py +15 -0
- bazis/contrib/authing/services/password/routes.py +63 -0
- bazis/contrib/authing/services/password/schemas.py +25 -0
- bazis_authing-2.2.0.dist-info/METADATA +646 -0
- bazis_authing-2.2.0.dist-info/RECORD +25 -0
- bazis_authing-2.2.0.dist-info/WHEEL +5 -0
- bazis_authing-2.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
17
|
+
__version__ = version('bazis-authing')
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
__version__ = 'dev'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '2.2.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
from bazis.core.utils.apps import BaseConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthingConfig(BaseConfig):
|
|
21
|
+
name = 'bazis.contrib.authing'
|
|
22
|
+
verbose_name = _('Authing')
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
from pydantic import Field
|
|
18
|
+
|
|
19
|
+
from bazis.core.utils.schemas import BazisSettings
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Settings(BazisSettings):
|
|
23
|
+
BAZIS_AUTH_COOKIE_LIFETIME: int = Field(600, title=_('Authorization cookie lifetime'))
|
|
24
|
+
BAZIS_AUTH_KINDS: list[str] = Field(
|
|
25
|
+
[
|
|
26
|
+
'bazis.contrib.authing.services.password',
|
|
27
|
+
],
|
|
28
|
+
title=_('Authorization services'),
|
|
29
|
+
)
|
|
30
|
+
AUTHENTICATION_BACKENDS: list[str] = Field(
|
|
31
|
+
[
|
|
32
|
+
'django.contrib.auth.backends.ModelBackend',
|
|
33
|
+
],
|
|
34
|
+
title=_('Authentication backends'),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
settings = Settings()
|
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2024-07-04 07:02+0000\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
20
|
+
#: apps.py:7
|
|
21
|
+
msgid "Authing"
|
|
22
|
+
msgstr ""
|
|
23
|
+
|
|
24
|
+
#: conf.py:7
|
|
25
|
+
msgid "Name of the authorization cookie"
|
|
26
|
+
msgstr ""
|
|
27
|
+
|
|
28
|
+
#: conf.py:8
|
|
29
|
+
msgid "Authorization cookie lifetime"
|
|
30
|
+
msgstr ""
|
|
31
|
+
|
|
32
|
+
#: conf.py:12
|
|
33
|
+
msgid "Authorization services"
|
|
34
|
+
msgstr ""
|
|
35
|
+
|
|
36
|
+
#: conf.py:15
|
|
37
|
+
msgid "Authentication backends"
|
|
38
|
+
msgstr ""
|
|
39
|
+
|
|
40
|
+
#: services/password/routes.py:16
|
|
41
|
+
msgid "Authentication"
|
|
42
|
+
msgstr ""
|
|
43
|
+
|
|
44
|
+
#: routes.py:58 routes.py:61
|
|
45
|
+
msgid "User is not authorized."
|
|
46
|
+
msgstr ""
|
|
47
|
+
|
|
48
|
+
#: routes.py:76
|
|
49
|
+
msgid "Next action"
|
|
50
|
+
msgstr ""
|
|
51
|
+
|
|
52
|
+
#: routes.py:88
|
|
53
|
+
msgid "Authentication error"
|
|
54
|
+
msgstr ""
|
|
55
|
+
|
|
56
|
+
#: services/password/__init__.py:11
|
|
57
|
+
msgid "Login/Password"
|
|
58
|
+
msgstr ""
|
|
Binary file
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2025-03-29 00:27+0300\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
|
20
|
+
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
|
|
21
|
+
"(n%100>=11 && n%100<=14)? 2 : 3);\n"
|
|
22
|
+
|
|
23
|
+
#: apps.py:7
|
|
24
|
+
msgid "Authing"
|
|
25
|
+
msgstr "Авторизация"
|
|
26
|
+
|
|
27
|
+
#: conf.py:7
|
|
28
|
+
msgid "Authorization cookie lifetime"
|
|
29
|
+
msgstr "Время жизни куки авторизации"
|
|
30
|
+
|
|
31
|
+
#: conf.py:13
|
|
32
|
+
msgid "Authorization services"
|
|
33
|
+
msgstr "Сервисы авторизации"
|
|
34
|
+
|
|
35
|
+
#: conf.py:19
|
|
36
|
+
msgid "Authentication backends"
|
|
37
|
+
msgstr "Бекенды аутентификации"
|
|
38
|
+
|
|
39
|
+
#: services/password/routes.py:15
|
|
40
|
+
msgid "Authentication"
|
|
41
|
+
msgstr "Аутентификация"
|
|
42
|
+
|
|
43
|
+
#: routes.py:58 routes.py:61
|
|
44
|
+
msgid "User is not authorized."
|
|
45
|
+
msgstr "Пользователь не авторизован"
|
|
46
|
+
|
|
47
|
+
#: routes.py:77
|
|
48
|
+
msgid "Next action"
|
|
49
|
+
msgstr "Следующее действие"
|
|
50
|
+
|
|
51
|
+
#: routes.py:91
|
|
52
|
+
msgid "Authentication error"
|
|
53
|
+
msgstr "Ошибка аутентификации"
|
|
54
|
+
|
|
55
|
+
#: services/google/__init__.py:12 services/google/routes.py:24
|
|
56
|
+
#, fuzzy
|
|
57
|
+
#| msgid "Authentication"
|
|
58
|
+
msgid "Google Authentication"
|
|
59
|
+
msgstr "Аутентификация"
|
|
60
|
+
|
|
61
|
+
#: services/google/conf.py:7
|
|
62
|
+
msgid "Google client ID"
|
|
63
|
+
msgstr ""
|
|
64
|
+
|
|
65
|
+
#: services/google/conf.py:8
|
|
66
|
+
msgid "Google client secret"
|
|
67
|
+
msgstr ""
|
|
68
|
+
|
|
69
|
+
#: services/google/conf.py:9
|
|
70
|
+
msgid "Google redirect URI"
|
|
71
|
+
msgstr ""
|
|
72
|
+
|
|
73
|
+
#: services/password/__init__.py:12
|
|
74
|
+
msgid "Login/Password"
|
|
75
|
+
msgstr "Логин/пароль"
|
|
76
|
+
|
|
77
|
+
#~ msgid "Name of the authorization cookie"
|
|
78
|
+
#~ msgstr "Наименование авторизационной куки"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from bazis.contrib.authing.routes import router # noqa: F401
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.conf import settings
|
|
16
|
+
from django.utils.translation import gettext as _
|
|
17
|
+
|
|
18
|
+
from fastapi import Depends
|
|
19
|
+
|
|
20
|
+
from starlette.status import HTTP_100_CONTINUE, HTTP_401_UNAUTHORIZED, HTTP_422_UNPROCESSABLE_ENTITY
|
|
21
|
+
|
|
22
|
+
from bazis.contrib.users import get_user_model
|
|
23
|
+
from bazis.contrib.users.service import get_token_data, get_user_optional
|
|
24
|
+
from bazis.core.errors import JsonApiBazisError, JsonApiBazisException
|
|
25
|
+
from bazis.core.routing import BazisRouter
|
|
26
|
+
from bazis.core.utils.imp import import_module
|
|
27
|
+
|
|
28
|
+
from .schemas import AuthResponse
|
|
29
|
+
from .service import AuthStore
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
User = get_user_model()
|
|
33
|
+
|
|
34
|
+
router = BazisRouter(tags=[_('Authentication')])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get('/auth/', response_model=AuthResponse)
|
|
38
|
+
def auth(
|
|
39
|
+
auth_store: AuthStore = Depends(),
|
|
40
|
+
user: User = Depends(get_user_optional),
|
|
41
|
+
token_data: dict = Depends(get_token_data),
|
|
42
|
+
):
|
|
43
|
+
if user.is_anonymous:
|
|
44
|
+
if user_id := auth_store.user_id:
|
|
45
|
+
user = User.objects.filter(id=user_id).first()
|
|
46
|
+
|
|
47
|
+
services = []
|
|
48
|
+
for service_path in settings.BAZIS_AUTH_KINDS:
|
|
49
|
+
try:
|
|
50
|
+
services.append(import_module(service_path))
|
|
51
|
+
except ImportError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
if user and not user.is_anonymous:
|
|
55
|
+
auth_type = auth_store.auth_type or token_data.get('auth_type')
|
|
56
|
+
|
|
57
|
+
return AuthResponse(
|
|
58
|
+
user_id=user.id,
|
|
59
|
+
username=user.username,
|
|
60
|
+
first_name=user.first_name,
|
|
61
|
+
last_name=user.last_name,
|
|
62
|
+
email=user.email,
|
|
63
|
+
token=user.jwt_build(auth_type=auth_type),
|
|
64
|
+
logout_actions=(
|
|
65
|
+
[
|
|
66
|
+
s.get_logout_actions()
|
|
67
|
+
for s in services
|
|
68
|
+
if s.AUTH_CODE == auth_type and hasattr(s, 'get_logout_actions')
|
|
69
|
+
]
|
|
70
|
+
+ [None]
|
|
71
|
+
)[0],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
errors = [
|
|
75
|
+
JsonApiBazisError(
|
|
76
|
+
_('User is not authorized.'),
|
|
77
|
+
status=HTTP_401_UNAUTHORIZED,
|
|
78
|
+
code='UNAUTHORIZED',
|
|
79
|
+
title=_('User is not authorized.'),
|
|
80
|
+
meta_data={
|
|
81
|
+
'actions': [
|
|
82
|
+
s.get_login_action() for s in services if hasattr(s, 'get_login_action')
|
|
83
|
+
],
|
|
84
|
+
'token': auth_store.token.value,
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
if auth_store.actions:
|
|
90
|
+
errors.append(
|
|
91
|
+
JsonApiBazisError(
|
|
92
|
+
auth_store.actions.name,
|
|
93
|
+
status=HTTP_100_CONTINUE,
|
|
94
|
+
code=auth_store.actions.code,
|
|
95
|
+
title=_('Next action'),
|
|
96
|
+
meta_data={
|
|
97
|
+
'actions': auth_store.actions.values,
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if auth_store.errors:
|
|
103
|
+
for err in auth_store.errors:
|
|
104
|
+
errors.append(
|
|
105
|
+
JsonApiBazisError(
|
|
106
|
+
err.detail,
|
|
107
|
+
status=HTTP_422_UNPROCESSABLE_ENTITY,
|
|
108
|
+
code=err.code,
|
|
109
|
+
title=_('Authentication error'),
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
raise JsonApiBazisException(errors, cookies=[auth_store.cookie])
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from uuid import UUID
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthResponse(BaseModel):
|
|
21
|
+
user_id: UUID | int
|
|
22
|
+
username: str
|
|
23
|
+
first_name: str | None = None
|
|
24
|
+
last_name: str | None = None
|
|
25
|
+
email: str | None = None
|
|
26
|
+
token: str
|
|
27
|
+
logout_actions: list[dict] | None = None
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from collections import UserDict
|
|
17
|
+
from dataclasses import asdict, dataclass
|
|
18
|
+
from secrets import token_hex
|
|
19
|
+
|
|
20
|
+
from django.conf import settings
|
|
21
|
+
from django.contrib.auth.signals import user_logged_in
|
|
22
|
+
from django.core.cache import cache
|
|
23
|
+
from django.utils.functional import cached_property
|
|
24
|
+
|
|
25
|
+
from fastapi import Cookie, Depends, Header, Query
|
|
26
|
+
from fastapi.security.utils import get_authorization_scheme_param
|
|
27
|
+
|
|
28
|
+
from starlette.requests import Request
|
|
29
|
+
|
|
30
|
+
from jose import jwt
|
|
31
|
+
|
|
32
|
+
from bazis.contrib.users.service import get_anonymous_user_model, get_user_model
|
|
33
|
+
from bazis.core.errors import JsonApi401Exception
|
|
34
|
+
from bazis.core.utils.functools import CtxToggle
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
LOG = logging.getLogger()
|
|
38
|
+
User = get_user_model()
|
|
39
|
+
AnonymousUser = get_anonymous_user_model()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class AuthToken:
|
|
44
|
+
key: str = None
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def parse(cls, data, required=False):
|
|
48
|
+
if not data:
|
|
49
|
+
if required:
|
|
50
|
+
raise JsonApi401Exception(detail='Token required')
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
token_data = jwt.decode(
|
|
55
|
+
data, settings.SECRET_KEY, algorithms=settings.BAZIS_JWT_SESSION_ALG
|
|
56
|
+
)
|
|
57
|
+
except Exception:
|
|
58
|
+
raise JsonApi401Exception(detail='Token is invalid') from None
|
|
59
|
+
|
|
60
|
+
return cls(
|
|
61
|
+
key=token_data['sub'],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def new(cls):
|
|
66
|
+
return cls(
|
|
67
|
+
key=token_hex(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@cached_property
|
|
71
|
+
def value(self):
|
|
72
|
+
return jwt.encode(
|
|
73
|
+
{
|
|
74
|
+
'sub': self.key,
|
|
75
|
+
},
|
|
76
|
+
settings.SECRET_KEY,
|
|
77
|
+
algorithm=settings.BAZIS_JWT_SESSION_ALG,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class AuthActions:
|
|
83
|
+
values: list
|
|
84
|
+
code: str
|
|
85
|
+
name: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class AuthError:
|
|
90
|
+
code: str
|
|
91
|
+
detail: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_token_header(authorization: str | None = Header(default=None, alias='Authorization')):
|
|
95
|
+
scheme, param = get_authorization_scheme_param(authorization)
|
|
96
|
+
if authorization and scheme.lower() == 'bearer':
|
|
97
|
+
return param
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AuthStore(UserDict):
|
|
101
|
+
token = None
|
|
102
|
+
cache_prefix = 'auth_store_'
|
|
103
|
+
token_required = False
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
token_param: str | None = Query(default=None, alias=settings.BAZIS_AUTH_COOKIE_NAME),
|
|
108
|
+
token_cookie: str | None = Cookie(default=None, alias=settings.BAZIS_AUTH_COOKIE_NAME),
|
|
109
|
+
token_header: str | None = Depends(get_token_header),
|
|
110
|
+
):
|
|
111
|
+
super().__init__()
|
|
112
|
+
self.push_toggle = CtxToggle()
|
|
113
|
+
self.token = AuthToken.parse(
|
|
114
|
+
token_param or token_header or token_cookie, required=self.token_required
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if self.token:
|
|
118
|
+
self.data = cache.get(f'{self.cache_prefix}{self.token.key}')
|
|
119
|
+
if self.data is None:
|
|
120
|
+
if self.token_required:
|
|
121
|
+
raise JsonApi401Exception(detail='Token has been expired')
|
|
122
|
+
else:
|
|
123
|
+
self.token = None
|
|
124
|
+
|
|
125
|
+
if not self.token:
|
|
126
|
+
self.token = AuthToken.new()
|
|
127
|
+
self.data_reset()
|
|
128
|
+
|
|
129
|
+
@cached_property
|
|
130
|
+
def cookie(self):
|
|
131
|
+
# the authorization cookie is set indefinitely
|
|
132
|
+
return settings.BAZIS_AUTH_COOKIE_NAME, self.token.value, 31536000
|
|
133
|
+
|
|
134
|
+
@cached_property
|
|
135
|
+
def as_param(self):
|
|
136
|
+
return f'{settings.BAZIS_AUTH_COOKIE_NAME}={self.token.value}'
|
|
137
|
+
|
|
138
|
+
def response_set_cookie(self, response):
|
|
139
|
+
key, value, max_age = self.cookie
|
|
140
|
+
# set the cookie
|
|
141
|
+
response.set_cookie(key=key, value=value, max_age=max_age)
|
|
142
|
+
return response
|
|
143
|
+
|
|
144
|
+
def _push_data(self):
|
|
145
|
+
if self.push_toggle.allow:
|
|
146
|
+
cache.set(
|
|
147
|
+
f'{self.cache_prefix}{self.token.key}',
|
|
148
|
+
self.data,
|
|
149
|
+
settings.BAZIS_AUTH_COOKIE_LIFETIME,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def data_reset(self):
|
|
153
|
+
self.data = {}
|
|
154
|
+
self._push_data()
|
|
155
|
+
|
|
156
|
+
def __setitem__(self, k, v) -> None:
|
|
157
|
+
super().__setitem__(k, v)
|
|
158
|
+
self._push_data()
|
|
159
|
+
|
|
160
|
+
def update(self, d, **kwargs) -> None:
|
|
161
|
+
with self.push_toggle:
|
|
162
|
+
super().update(d, **kwargs)
|
|
163
|
+
self._push_data()
|
|
164
|
+
|
|
165
|
+
def __delitem__(self, v) -> None:
|
|
166
|
+
super().__delitem__(v)
|
|
167
|
+
self._push_data()
|
|
168
|
+
|
|
169
|
+
def clear(self) -> None:
|
|
170
|
+
with self.push_toggle:
|
|
171
|
+
super().clear()
|
|
172
|
+
self._push_data()
|
|
173
|
+
|
|
174
|
+
def login(self, user, request: Request, auth_type: str):
|
|
175
|
+
user_logged_in.send(sender=user.__class__, request=request, user=user)
|
|
176
|
+
self.data['_user_id'] = user.id
|
|
177
|
+
self.data['_auth_type'] = auth_type
|
|
178
|
+
self._push_data()
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def user_id(self):
|
|
182
|
+
return self.data.get('_user_id')
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def auth_type(self):
|
|
186
|
+
return self.data.get('_auth_type')
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def errors(self) -> list[AuthError]:
|
|
190
|
+
if errors := self.data.get('_errors'):
|
|
191
|
+
return [AuthError(**err) for err in errors]
|
|
192
|
+
|
|
193
|
+
def set_error(self, code, detail=None):
|
|
194
|
+
LOG.info('AuthStore set_error: %s, %s', code, detail)
|
|
195
|
+
err = AuthError(code, detail)
|
|
196
|
+
errors = self.data.get('errors', [])
|
|
197
|
+
errors.append(asdict(err))
|
|
198
|
+
self.data['_errors'] = errors
|
|
199
|
+
self._push_data()
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def actions(self) -> AuthActions:
|
|
203
|
+
if actions := self.data.get('_actions'):
|
|
204
|
+
return AuthActions(**actions)
|
|
205
|
+
|
|
206
|
+
def set_actions(self, values: list, code: str, name: str = None):
|
|
207
|
+
self.data['_actions'] = asdict(AuthActions(values, code, name))
|
|
208
|
+
self._push_data()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class AuthStoreTokenRequired(AuthStore):
|
|
212
|
+
token_required = True
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
AUTH_CODE = 'google'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_login_action():
|
|
22
|
+
from bazis.core.app import app
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
'code': AUTH_CODE,
|
|
26
|
+
'name': _('Google Authentication'),
|
|
27
|
+
'url': app.router.url_path_for('google_auth_init'),
|
|
28
|
+
'method': 'GET',
|
|
29
|
+
}
|