hyperpocket 0.2.1__py3-none-any.whl → 0.3.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.
- hyperpocket/auth/reddit/oauth2_handler.py +1 -1
- hyperpocket/auth/slack/oauth2_handler.py +1 -1
- hyperpocket/cli/__main__.py +4 -2
- hyperpocket/cli/auth_oauth2.py +90 -0
- hyperpocket/cli/{auth.py → auth_token.py} +32 -25
- hyperpocket/cli/codegen/auth/__init__.py +8 -1
- hyperpocket/cli/codegen/auth/auth_oauth2_context_template.py +38 -0
- hyperpocket/cli/codegen/auth/auth_oauth2_handler_template.py +158 -0
- hyperpocket/cli/codegen/auth/auth_oauth2_schema_template.py +20 -0
- hyperpocket/cli/codegen/auth/server_auth_template.py +34 -3
- hyperpocket/pocket_core.py +58 -32
- hyperpocket/pocket_main.py +1 -1
- hyperpocket-0.3.0.dist-info/METADATA +377 -0
- {hyperpocket-0.2.1.dist-info → hyperpocket-0.3.0.dist-info}/RECORD +16 -12
- hyperpocket-0.2.1.dist-info/METADATA +0 -326
- {hyperpocket-0.2.1.dist-info → hyperpocket-0.3.0.dist-info}/WHEEL +0 -0
- {hyperpocket-0.2.1.dist-info → hyperpocket-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -53,7 +53,7 @@ class RedditOAuth2AuthHandler(AuthHandlerInterface):
|
|
53
53
|
config().public_base_url + "/",
|
54
54
|
f"{config().callback_url_rewrite_prefix}/auth/reddit/oauth2/callback",
|
55
55
|
)
|
56
|
-
|
56
|
+
|
57
57
|
auth_url = self._make_auth_url(
|
58
58
|
req=auth_req, redirect_uri=redirect_uri, state=future_uid
|
59
59
|
)
|
@@ -59,7 +59,7 @@ class SlackOAuth2AuthHandler(AuthHandlerInterface):
|
|
59
59
|
config().public_base_url + "/",
|
60
60
|
f"{config().callback_url_rewrite_prefix}/auth/slack/oauth2/callback",
|
61
61
|
)
|
62
|
-
|
62
|
+
|
63
63
|
auth_url = self._make_auth_url(
|
64
64
|
req=auth_req, redirect_uri=redirect_uri, state=future_uid
|
65
65
|
)
|
hyperpocket/cli/__main__.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
import click
|
2
2
|
|
3
|
-
from hyperpocket.cli.auth import create_token_auth_template
|
4
3
|
from hyperpocket.cli.eject import eject
|
5
4
|
from hyperpocket.cli.pull import pull
|
6
5
|
from hyperpocket.cli.sync import sync
|
7
|
-
|
6
|
+
from hyperpocket.cli.eject import eject
|
7
|
+
from hyperpocket.cli.auth_token import create_token_auth_template
|
8
|
+
from hyperpocket.cli.auth_oauth2 import create_oauth2_auth_template
|
8
9
|
|
9
10
|
@click.group()
|
10
11
|
def cli():
|
@@ -18,6 +19,7 @@ def devtool():
|
|
18
19
|
|
19
20
|
cli.add_command(devtool)
|
20
21
|
devtool.add_command(create_token_auth_template)
|
22
|
+
devtool.add_command(create_oauth2_auth_template)
|
21
23
|
|
22
24
|
cli.add_command(pull)
|
23
25
|
cli.add_command(sync)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import os
|
2
|
+
import click
|
3
|
+
from pathlib import Path
|
4
|
+
from hyperpocket.cli.codegen.auth import get_auth_context_template, get_auth_oauth2_context_template, get_auth_oauth2_handler_template, get_auth_oauth2_schema_template, get_server_auth_oauth2_template
|
5
|
+
|
6
|
+
@click.command()
|
7
|
+
@click.argument('service_name', type=str)
|
8
|
+
@click.option('--force', is_flag=True, default=False)
|
9
|
+
def create_oauth2_auth_template(service_name, force):
|
10
|
+
## Validate service_name
|
11
|
+
if not service_name.islower() or not service_name.replace('_', '').isalpha():
|
12
|
+
raise ValueError("service_name must be lowercase and contain only letters and underscores")
|
13
|
+
|
14
|
+
capitliazed_service_name = service_name.capitalize()
|
15
|
+
if '_' in service_name:
|
16
|
+
capitliazed_service_name = ''.join([word.capitalize() for word in service_name.split('_')])
|
17
|
+
|
18
|
+
## Get the current working directory
|
19
|
+
cwd = Path.cwd()
|
20
|
+
parent_path = cwd.parent
|
21
|
+
|
22
|
+
generate_server_auth(service_name, parent_path, force)
|
23
|
+
generate_hyperpocket_auth_dir(service_name, parent_path, force)
|
24
|
+
generate_auth_context(service_name, capitliazed_service_name, parent_path, force)
|
25
|
+
generate_auth_oauth2_context(service_name, capitliazed_service_name, parent_path, force)
|
26
|
+
generate_auth_oauth2_handler(service_name, capitliazed_service_name, parent_path, force)
|
27
|
+
generate_auth_oauth2_schema(service_name, capitliazed_service_name, parent_path, force)
|
28
|
+
##TODO: Add service to hyperpocket/auth/provider
|
29
|
+
|
30
|
+
def generate_server_auth(service_name, parent_path, force):
|
31
|
+
print(f"Generating server/auth for '{service_name}'.")
|
32
|
+
output_from_parsed_template = get_server_auth_oauth2_template().render(service_name=service_name)
|
33
|
+
output_path = parent_path / f'hyperpocket/hyperpocket/server/auth/{service_name}.py'
|
34
|
+
if not output_path.exists() or force:
|
35
|
+
with open(output_path, "w") as f:
|
36
|
+
f.write(output_from_parsed_template)
|
37
|
+
|
38
|
+
def generate_hyperpocket_auth_dir(service_name, parent_path, force):
|
39
|
+
if not os.path.exists(parent_path / f'hyperpocket/hyperpocket/auth/{service_name}'):
|
40
|
+
os.makedirs(parent_path / f'hyperpocket/hyperpocket/auth/{service_name}')
|
41
|
+
|
42
|
+
output_path = parent_path / f'hyperpocket/hyperpocket/auth/{service_name}/__init__.py'
|
43
|
+
if not output_path.exists() or force:
|
44
|
+
with open(output_path, "w") as f:
|
45
|
+
pass
|
46
|
+
|
47
|
+
def generate_auth_context(service_name, capitliazed_service_name, parent_path, force):
|
48
|
+
print(f"Generating auth/context for '{service_name}'.")
|
49
|
+
output_from_parsed_template = get_auth_context_template().render(
|
50
|
+
capitalized_service_name=capitliazed_service_name,
|
51
|
+
upper_service_name=service_name.upper()
|
52
|
+
)
|
53
|
+
output_path = parent_path / f'hyperpocket/hyperpocket/auth/{service_name}/context.py'
|
54
|
+
if not output_path.exists() or force:
|
55
|
+
with open(output_path, "w") as f:
|
56
|
+
f.write(output_from_parsed_template)
|
57
|
+
|
58
|
+
def generate_auth_oauth2_context(service_name, capitliazed_service_name, parent_path, force):
|
59
|
+
print(f"Generating auth/oauth2 context for '{service_name}'.")
|
60
|
+
output_from_parsed_template = get_auth_oauth2_context_template().render(
|
61
|
+
service_name = service_name,
|
62
|
+
capitalized_service_name=capitliazed_service_name,
|
63
|
+
)
|
64
|
+
output_path = parent_path / f'hyperpocket/hyperpocket/auth/{service_name}/oauth2_context.py'
|
65
|
+
if not output_path.exists() or force:
|
66
|
+
with open(output_path, "w") as f:
|
67
|
+
f.write(output_from_parsed_template)
|
68
|
+
|
69
|
+
def generate_auth_oauth2_handler(service_name, capitliazed_service_name, parent_path, force):
|
70
|
+
print(f"Generating auth/oauth2 handler for '{service_name}'.")
|
71
|
+
output_from_parsed_template = get_auth_oauth2_handler_template().render(
|
72
|
+
service_name = service_name,
|
73
|
+
auth_handler_name = service_name.replace('_','-'),
|
74
|
+
capitalized_service_name=capitliazed_service_name,
|
75
|
+
upper_service_name=service_name.upper()
|
76
|
+
)
|
77
|
+
output_path = parent_path / f'hyperpocket/hyperpocket/auth/{service_name}/oauth2_handler.py'
|
78
|
+
if not output_path.exists() or force:
|
79
|
+
with open(output_path, "w") as f:
|
80
|
+
f.write(output_from_parsed_template)
|
81
|
+
|
82
|
+
def generate_auth_oauth2_schema(service_name, capitliazed_service_name, parent_path, force):
|
83
|
+
print(f"Generating auth/oauth2 schema for '{service_name}'.")
|
84
|
+
output_from_parsed_template = get_auth_oauth2_schema_template().render(
|
85
|
+
capitalized_service_name=capitliazed_service_name,
|
86
|
+
)
|
87
|
+
output_path = parent_path / f'hyperpocket/hyperpocket/auth/{service_name}/oauth2_schema.py'
|
88
|
+
if not output_path.exists() or force:
|
89
|
+
with open(output_path, "w") as f:
|
90
|
+
f.write(output_from_parsed_template)
|
@@ -14,7 +14,8 @@ from hyperpocket.cli.codegen.auth import (
|
|
14
14
|
|
15
15
|
@click.command()
|
16
16
|
@click.argument("service_name", type=str)
|
17
|
-
|
17
|
+
@click.option("--force", is_flag=True, default=False)
|
18
|
+
def create_token_auth_template(service_name, force):
|
18
19
|
## Validate service_name
|
19
20
|
if not service_name.islower() or not service_name.replace("_", "").isalpha():
|
20
21
|
raise ValueError(
|
@@ -31,37 +32,39 @@ def create_token_auth_template(service_name):
|
|
31
32
|
cwd = Path.cwd()
|
32
33
|
parent_path = cwd.parent
|
33
34
|
|
34
|
-
generate_server_auth(service_name, parent_path)
|
35
|
-
generate_hyperpocket_auth_dir(service_name, parent_path)
|
36
|
-
generate_auth_context(service_name, capitliazed_service_name, parent_path)
|
37
|
-
generate_auth_token_context(service_name, capitliazed_service_name, parent_path)
|
38
|
-
generate_auth_token_handler(service_name, capitliazed_service_name, parent_path)
|
39
|
-
generate_auth_token_schema(service_name, capitliazed_service_name, parent_path)
|
35
|
+
generate_server_auth(service_name, parent_path, force)
|
36
|
+
generate_hyperpocket_auth_dir(service_name, parent_path, force)
|
37
|
+
generate_auth_context(service_name, capitliazed_service_name, parent_path, force)
|
38
|
+
generate_auth_token_context(service_name, capitliazed_service_name, parent_path, force)
|
39
|
+
generate_auth_token_handler(service_name, capitliazed_service_name, parent_path, force)
|
40
|
+
generate_auth_token_schema(service_name, capitliazed_service_name, parent_path, force)
|
40
41
|
##TODO: Add service to hyperpocket/auth/provider
|
41
42
|
|
42
43
|
|
43
|
-
def generate_server_auth(service_name, parent_path):
|
44
|
+
def generate_server_auth(service_name, parent_path, force):
|
44
45
|
print(f"Generating server/auth for '{service_name}'.")
|
45
46
|
output_from_parsed_template = get_server_auth_token_template().render(
|
46
47
|
service_name=service_name
|
47
48
|
)
|
48
49
|
output_path = parent_path / f"hyperpocket/hyperpocket/server/auth/{service_name}.py"
|
49
|
-
|
50
|
-
|
50
|
+
if not output_path.exists() or force:
|
51
|
+
with open(output_path, "w") as f:
|
52
|
+
f.write(output_from_parsed_template)
|
51
53
|
|
52
54
|
|
53
|
-
def generate_hyperpocket_auth_dir(service_name, parent_path):
|
55
|
+
def generate_hyperpocket_auth_dir(service_name, parent_path, force):
|
54
56
|
if not os.path.exists(parent_path / f"hyperpocket/hyperpocket/auth/{service_name}"):
|
55
57
|
os.makedirs(parent_path / f"hyperpocket/hyperpocket/auth/{service_name}")
|
56
58
|
|
57
59
|
output_path = (
|
58
60
|
parent_path / f"hyperpocket/hyperpocket/auth/{service_name}/__init__.py"
|
59
61
|
)
|
60
|
-
|
61
|
-
|
62
|
+
if not output_path.exists() or force:
|
63
|
+
with open(output_path, "w"):
|
64
|
+
pass
|
62
65
|
|
63
66
|
|
64
|
-
def generate_auth_context(service_name, capitliazed_service_name, parent_path):
|
67
|
+
def generate_auth_context(service_name, capitliazed_service_name, parent_path, force):
|
65
68
|
print(f"Generating auth/context for '{service_name}'.")
|
66
69
|
output_from_parsed_template = get_auth_context_template().render(
|
67
70
|
caplitalized_service_name=capitliazed_service_name,
|
@@ -70,11 +73,12 @@ def generate_auth_context(service_name, capitliazed_service_name, parent_path):
|
|
70
73
|
output_path = (
|
71
74
|
parent_path / f"hyperpocket/hyperpocket/auth/{service_name}/context.py"
|
72
75
|
)
|
73
|
-
|
74
|
-
|
76
|
+
if not output_path.exists() or force:
|
77
|
+
with open(output_path, "w") as f:
|
78
|
+
f.write(output_from_parsed_template)
|
75
79
|
|
76
80
|
|
77
|
-
def generate_auth_token_context(service_name, capitliazed_service_name, parent_path):
|
81
|
+
def generate_auth_token_context(service_name, capitliazed_service_name, parent_path, force):
|
78
82
|
print(f"Generating auth/token context for '{service_name}'.")
|
79
83
|
output_from_parsed_template = get_auth_token_context_template().render(
|
80
84
|
service_name=service_name,
|
@@ -83,11 +87,12 @@ def generate_auth_token_context(service_name, capitliazed_service_name, parent_p
|
|
83
87
|
output_path = (
|
84
88
|
parent_path / f"hyperpocket/hyperpocket/auth/{service_name}/token_context.py"
|
85
89
|
)
|
86
|
-
|
87
|
-
|
90
|
+
if not output_path.exists() or force:
|
91
|
+
with open(output_path, "w") as f:
|
92
|
+
f.write(output_from_parsed_template)
|
88
93
|
|
89
94
|
|
90
|
-
def generate_auth_token_handler(service_name, capitliazed_service_name, parent_path):
|
95
|
+
def generate_auth_token_handler(service_name, capitliazed_service_name, parent_path, force):
|
91
96
|
print(f"Generating auth/token handler for '{service_name}'.")
|
92
97
|
output_from_parsed_template = get_auth_token_handler_template().render(
|
93
98
|
service_name=service_name,
|
@@ -98,11 +103,12 @@ def generate_auth_token_handler(service_name, capitliazed_service_name, parent_p
|
|
98
103
|
output_path = (
|
99
104
|
parent_path / f"hyperpocket/hyperpocket/auth/{service_name}/token_handler.py"
|
100
105
|
)
|
101
|
-
|
102
|
-
|
106
|
+
if not output_path.exists() or force:
|
107
|
+
with open(output_path, "w") as f:
|
108
|
+
f.write(output_from_parsed_template)
|
103
109
|
|
104
110
|
|
105
|
-
def generate_auth_token_schema(service_name, capitliazed_service_name, parent_path):
|
111
|
+
def generate_auth_token_schema(service_name, capitliazed_service_name, parent_path, force):
|
106
112
|
print(f"Generating auth/token schema for '{service_name}'.")
|
107
113
|
output_from_parsed_template = get_auth_token_schema_template().render(
|
108
114
|
caplitalized_service_name=capitliazed_service_name,
|
@@ -110,5 +116,6 @@ def generate_auth_token_schema(service_name, capitliazed_service_name, parent_pa
|
|
110
116
|
output_path = (
|
111
117
|
parent_path / f"hyperpocket/hyperpocket/auth/{service_name}/token_schema.py"
|
112
118
|
)
|
113
|
-
|
114
|
-
|
119
|
+
if not output_path.exists() or force:
|
120
|
+
with open(output_path, "w") as f:
|
121
|
+
f.write(output_from_parsed_template)
|
@@ -1,13 +1,20 @@
|
|
1
1
|
from .auth_context_template import get_auth_context_template
|
2
|
+
from .auth_oauth2_context_template import get_auth_oauth2_context_template
|
3
|
+
from .auth_oauth2_handler_template import get_auth_oauth2_handler_template
|
4
|
+
from .auth_oauth2_schema_template import get_auth_oauth2_schema_template
|
2
5
|
from .auth_token_context_template import get_auth_token_context_template
|
3
6
|
from .auth_token_handler_template import get_auth_token_handler_template
|
4
7
|
from .auth_token_schema_template import get_auth_token_schema_template
|
5
|
-
from .server_auth_template import get_server_auth_token_template
|
8
|
+
from .server_auth_template import get_server_auth_token_template, get_server_auth_oauth2_template
|
6
9
|
|
7
10
|
__all__ = [
|
8
11
|
"get_auth_context_template",
|
12
|
+
"get_auth_oauth2_context_template",
|
13
|
+
"get_auth_oauth2_handler_template",
|
14
|
+
"get_auth_oauth2_schema_template",
|
9
15
|
"get_auth_token_context_template",
|
10
16
|
"get_auth_token_handler_template",
|
11
17
|
"get_auth_token_schema_template",
|
12
18
|
"get_server_auth_token_template",
|
19
|
+
"get_server_auth_oauth2_template",
|
13
20
|
]
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from jinja2 import Template
|
2
|
+
|
3
|
+
def get_auth_oauth2_context_template() -> Template:
|
4
|
+
return Template('''
|
5
|
+
from datetime import datetime, timedelta, timezone
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
from pydantic import Field
|
9
|
+
|
10
|
+
from hyperpocket.auth.{{ service_name }}.context import {{ capitalized_service_name }}AuthContext
|
11
|
+
from hyperpocket.auth.{{ service_name }}.oauth2_schema import {{ capitalized_service_name }}OAuth2Response
|
12
|
+
|
13
|
+
|
14
|
+
class {{ capitalized_service_name }}OAuth2AuthContext({{ capitalized_service_name }}AuthContext):
|
15
|
+
refresh_token: Optional[str] = Field(default=None, description="refresh token")
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def from_{{ service_name }}_oauth2_response(cls, response: {{ capitalized_service_name }}OAuth2Response):
|
19
|
+
description = f'{{ capitalized_service_name }} OAuth2 Context logged in as a user {response.authed_user.id}'
|
20
|
+
now = datetime.now(tz=timezone.utc)
|
21
|
+
|
22
|
+
access_token = response.access_token
|
23
|
+
refresh_token = response.refresh_token
|
24
|
+
expires_in = response.expires_in
|
25
|
+
|
26
|
+
if expires_in:
|
27
|
+
expires_at = now + timedelta(seconds=response.authed_user.expires_in)
|
28
|
+
else:
|
29
|
+
expires_at = None
|
30
|
+
|
31
|
+
return cls(
|
32
|
+
access_token=access_token,
|
33
|
+
refresh_token=refresh_token,
|
34
|
+
expires_at=expires_at,
|
35
|
+
description=description,
|
36
|
+
detail=response,
|
37
|
+
)
|
38
|
+
''')
|
@@ -0,0 +1,158 @@
|
|
1
|
+
from jinja2 import Template
|
2
|
+
|
3
|
+
def get_auth_oauth2_handler_template() -> Template:
|
4
|
+
return Template('''
|
5
|
+
from typing import Optional
|
6
|
+
from urllib.parse import urljoin, urlencode
|
7
|
+
|
8
|
+
import httpx
|
9
|
+
|
10
|
+
from hyperpocket.auth import AuthProvider
|
11
|
+
from hyperpocket.auth.context import AuthContext
|
12
|
+
from hyperpocket.auth.handler import AuthHandlerInterface
|
13
|
+
from hyperpocket.auth.{{ service_name }}.oauth2_context import {{ capitalized_service_name }}OAuth2AuthContext
|
14
|
+
from hyperpocket.auth.{{ service_name }}.oauth2_schema import {{ capitalized_service_name }}OAuth2Response, {{ capitalized_service_name }}OAuth2Request
|
15
|
+
from hyperpocket.config import config as config
|
16
|
+
from hyperpocket.futures import FutureStore
|
17
|
+
|
18
|
+
|
19
|
+
class {{ capitalized_service_name }}OAuth2AuthHandler(AuthHandlerInterface):
|
20
|
+
_{{ upper_service_name }}_OAUTH_URL: str = "" # e.g. "https://slack.com/oauth/v2/authorize"
|
21
|
+
_{{ upper_service_name }}_TOKEN_URL: str = "" # e.g. "https://slack.com/api/oauth.v2.access"
|
22
|
+
|
23
|
+
name: str = "{{ auth_handler_name }}-oauth2"
|
24
|
+
description: str = "This handler is used to authenticate users using the {{ capitalized_service_name }} OAuth2 authentication method."
|
25
|
+
scoped: bool = True
|
26
|
+
|
27
|
+
@staticmethod
|
28
|
+
def provider() -> AuthProvider:
|
29
|
+
return AuthProvider.{{ upper_service_name }}
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def provider_default() -> bool:
|
33
|
+
return True
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def recommended_scopes() -> set[str]:
|
37
|
+
"""
|
38
|
+
This method returns a set of recommended scopes for the service.
|
39
|
+
If the service has a recommended scope, it should be returned here.
|
40
|
+
Example:
|
41
|
+
return {
|
42
|
+
"channels:history",
|
43
|
+
"channels:read",
|
44
|
+
"chat:write",
|
45
|
+
"groups:history",
|
46
|
+
"groups:read",
|
47
|
+
"im:history",
|
48
|
+
"mpim:history",
|
49
|
+
"reactions:read",
|
50
|
+
"reactions:write",
|
51
|
+
}
|
52
|
+
"""
|
53
|
+
return set()
|
54
|
+
|
55
|
+
def prepare(self, auth_req: {{ capitalized_service_name }}OAuth2Request, thread_id: str, profile: str,
|
56
|
+
future_uid: str, *args, **kwargs) -> str:
|
57
|
+
redirect_uri = urljoin(
|
58
|
+
config.public_base_url + "/",
|
59
|
+
f"{config.callback_url_rewrite_prefix}/auth/{{ service_name }}/oauth2/callback",
|
60
|
+
)
|
61
|
+
print(f"redirect_uri: {redirect_uri}")
|
62
|
+
auth_url = self._make_auth_url(req=auth_req, redirect_uri=redirect_uri, state=future_uid)
|
63
|
+
|
64
|
+
FutureStore.create_future(future_uid, data={
|
65
|
+
"redirect_uri": redirect_uri,
|
66
|
+
"thread_id": thread_id,
|
67
|
+
"profile": profile,
|
68
|
+
})
|
69
|
+
|
70
|
+
return f'User needs to authenticate using the following URL: {auth_url}'
|
71
|
+
|
72
|
+
async def authenticate(self, auth_req: {{ capitalized_service_name }}OAuth2Request, future_uid: str, *args, **kwargs) -> AuthContext:
|
73
|
+
future_data = FutureStore.get_future(future_uid)
|
74
|
+
auth_code = await future_data.future
|
75
|
+
|
76
|
+
async with httpx.AsyncClient() as client:
|
77
|
+
resp = await client.post(
|
78
|
+
url=self._{{ upper_service_name }}_TOKEN_URL,
|
79
|
+
data={
|
80
|
+
'client_id': auth_req.client_id,
|
81
|
+
'client_secret': auth_req.client_secret,
|
82
|
+
'code': auth_code,
|
83
|
+
'redirect_uri': future_data.data["redirect_uri"],
|
84
|
+
}
|
85
|
+
)
|
86
|
+
if resp.status_code != 200:
|
87
|
+
raise Exception(f"failed to authenticate. status_code : {resp.status_code}")
|
88
|
+
|
89
|
+
resp_json = resp.json()
|
90
|
+
if resp_json["ok"] is False:
|
91
|
+
raise Exception(f"failed to authenticate. error : {resp_json['error']}")
|
92
|
+
|
93
|
+
resp_typed = {{ capitalized_service_name }}OAuth2Response(**resp_json)
|
94
|
+
return {{ capitalized_service_name }}OAuth2AuthContext.from_{{ service_name }}_oauth2_response(resp_typed)
|
95
|
+
|
96
|
+
async def refresh(self, auth_req: {{ capitalized_service_name }}OAuth2Request, context: AuthContext, *args, **kwargs) -> AuthContext:
|
97
|
+
{{ service_name }}_context: {{ capitalized_service_name }}OAuth2AuthContext = context
|
98
|
+
last_oauth2_resp: {{ capitalized_service_name }}OAuth2Response = {{ service_name }}_context.detail
|
99
|
+
refresh_token = {{ service_name }}_context.refresh_token
|
100
|
+
|
101
|
+
async with httpx.AsyncClient() as client:
|
102
|
+
resp = await client.post(
|
103
|
+
url=self._{{ upper_service_name }}_TOKEN_URL,
|
104
|
+
data={
|
105
|
+
'client_id': config.auth.{{ service_name }}.client_id,
|
106
|
+
'client_secret': config.auth.{{ service_name }}.client_secret,
|
107
|
+
'grant_type': 'refresh_token',
|
108
|
+
'refresh_token': refresh_token,
|
109
|
+
},
|
110
|
+
)
|
111
|
+
|
112
|
+
if resp.status_code != 200:
|
113
|
+
raise Exception(f"failed to refresh. status_code : {resp.status_code}")
|
114
|
+
|
115
|
+
resp_json = resp.json()
|
116
|
+
if resp_json["ok"] is False:
|
117
|
+
raise Exception(f"failed to refresh. status_code : {resp.status_code}")
|
118
|
+
|
119
|
+
if last_oauth2_resp.authed_user:
|
120
|
+
new_resp = last_oauth2_resp.model_copy(
|
121
|
+
update={
|
122
|
+
"authed_user": {{ capitalized_service_name }}OAuth2Response.AuthedUser(**{
|
123
|
+
**last_oauth2_resp.authed_user.model_dump(),
|
124
|
+
"access_token": resp_json["access_token"],
|
125
|
+
"refresh_token": resp_json["refresh_token"],
|
126
|
+
"expires_in": resp_json["expires_in"],
|
127
|
+
})
|
128
|
+
}
|
129
|
+
)
|
130
|
+
else:
|
131
|
+
new_resp = last_oauth2_resp.model_copy(
|
132
|
+
update={
|
133
|
+
**last_oauth2_resp.model_dump(),
|
134
|
+
"access_token": resp_json["access_token"],
|
135
|
+
"refresh_token": resp_json["refresh_token"],
|
136
|
+
"expires_in": resp_json["expires_in"],
|
137
|
+
}
|
138
|
+
)
|
139
|
+
|
140
|
+
return {{ capitalized_service_name }}OAuth2AuthContext.from_{{ service_name }}_oauth2_response(new_resp)
|
141
|
+
|
142
|
+
def _make_auth_url(self, req: {{ capitalized_service_name }}OAuth2Request, redirect_uri: str, state: str):
|
143
|
+
params = {
|
144
|
+
"user_scope": ','.join(req.auth_scopes),
|
145
|
+
"client_id": req.client_id,
|
146
|
+
"redirect_uri": redirect_uri,
|
147
|
+
"state": state,
|
148
|
+
}
|
149
|
+
auth_url = f"{self._{{ upper_service_name }}_OAUTH_URL}?{urlencode(params)}"
|
150
|
+
return auth_url
|
151
|
+
|
152
|
+
def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> {{ capitalized_service_name }}OAuth2Request:
|
153
|
+
return {{ capitalized_service_name }}OAuth2Request(
|
154
|
+
auth_scopes=auth_scopes,
|
155
|
+
client_id=config.auth.{{ service_name }}.client_id,
|
156
|
+
client_secret=config.auth.{{ service_name }}.client_secret,
|
157
|
+
)
|
158
|
+
''')
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from jinja2 import Template
|
2
|
+
|
3
|
+
def get_auth_oauth2_schema_template() -> Template:
|
4
|
+
return Template('''
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
|
10
|
+
|
11
|
+
|
12
|
+
class {{ capitalized_service_name }}OAuth2Request(AuthenticateRequest):
|
13
|
+
client_id: str
|
14
|
+
client_secret: str
|
15
|
+
|
16
|
+
class {{ capitalized_service_name }}OAuth2Response(AuthenticateResponse):
|
17
|
+
access_token: str
|
18
|
+
expires_in: int
|
19
|
+
refresh_token: Optional[str] = Field(default=None)
|
20
|
+
''')
|
@@ -2,7 +2,7 @@ from jinja2 import Template
|
|
2
2
|
|
3
3
|
|
4
4
|
def get_server_auth_token_template() -> Template:
|
5
|
-
return Template(
|
5
|
+
return Template('''
|
6
6
|
from fastapi import APIRouter
|
7
7
|
from starlette.responses import HTMLResponse
|
8
8
|
from hyperpocket.futures import FutureStore
|
@@ -15,5 +15,36 @@ async def {{ service_name }}_token_callback(state: str, token: str):
|
|
15
15
|
FutureStore.resolve_future(state, token)
|
16
16
|
except ValueError:
|
17
17
|
return HTMLResponse(content="failed")
|
18
|
-
return HTMLResponse(content="success")
|
19
|
-
|
18
|
+
return HTMLResponse(content="success")
|
19
|
+
''')
|
20
|
+
|
21
|
+
def get_server_auth_oauth2_template() -> Template:
|
22
|
+
return Template('''
|
23
|
+
from fastapi import APIRouter
|
24
|
+
from starlette.responses import HTMLResponse
|
25
|
+
from hyperpocket.futures import FutureStore
|
26
|
+
|
27
|
+
{{ service_name }}_auth_router = APIRouter(
|
28
|
+
prefix="/{{ service_name }}"
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
@{{ service_name }}_auth_router.get("/oauth2/callback")
|
33
|
+
async def {{ service_name }}_oauth2_callback(state: str, code: str):
|
34
|
+
try:
|
35
|
+
FutureStore.resolve_future(state, code)
|
36
|
+
except ValueError:
|
37
|
+
return HTMLResponse(content="failed")
|
38
|
+
|
39
|
+
return HTMLResponse(content="success")
|
40
|
+
|
41
|
+
|
42
|
+
@{{ service_name }}_auth_router.get("/token/callback")
|
43
|
+
async def {{ service_name }}_token_callback(state: str, token: str):
|
44
|
+
try:
|
45
|
+
FutureStore.resolve_future(state, token)
|
46
|
+
except ValueError:
|
47
|
+
return HTMLResponse(content="failed")
|
48
|
+
|
49
|
+
return HTMLResponse(content="success")
|
50
|
+
''')
|