hyperpocket 0.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. hyperpocket/__init__.py +7 -0
  2. hyperpocket/auth/README.KR.md +309 -0
  3. hyperpocket/auth/README.md +323 -0
  4. hyperpocket/auth/__init__.py +24 -0
  5. hyperpocket/auth/calendly/__init__.py +0 -0
  6. hyperpocket/auth/calendly/context.py +13 -0
  7. hyperpocket/auth/calendly/oauth2_context.py +25 -0
  8. hyperpocket/auth/calendly/oauth2_handler.py +146 -0
  9. hyperpocket/auth/calendly/oauth2_schema.py +16 -0
  10. hyperpocket/auth/context.py +38 -0
  11. hyperpocket/auth/github/__init__.py +0 -0
  12. hyperpocket/auth/github/context.py +13 -0
  13. hyperpocket/auth/github/oauth2_context.py +25 -0
  14. hyperpocket/auth/github/oauth2_handler.py +143 -0
  15. hyperpocket/auth/github/oauth2_schema.py +16 -0
  16. hyperpocket/auth/github/token_context.py +12 -0
  17. hyperpocket/auth/github/token_handler.py +79 -0
  18. hyperpocket/auth/github/token_schema.py +9 -0
  19. hyperpocket/auth/google/__init__.py +0 -0
  20. hyperpocket/auth/google/context.py +15 -0
  21. hyperpocket/auth/google/oauth2_context.py +31 -0
  22. hyperpocket/auth/google/oauth2_handler.py +137 -0
  23. hyperpocket/auth/google/oauth2_schema.py +18 -0
  24. hyperpocket/auth/handler.py +171 -0
  25. hyperpocket/auth/linear/__init__.py +0 -0
  26. hyperpocket/auth/linear/context.py +15 -0
  27. hyperpocket/auth/linear/token_context.py +15 -0
  28. hyperpocket/auth/linear/token_handler.py +68 -0
  29. hyperpocket/auth/linear/token_schema.py +9 -0
  30. hyperpocket/auth/provider.py +16 -0
  31. hyperpocket/auth/schema.py +19 -0
  32. hyperpocket/auth/slack/__init__.py +0 -0
  33. hyperpocket/auth/slack/context.py +15 -0
  34. hyperpocket/auth/slack/oauth2_context.py +40 -0
  35. hyperpocket/auth/slack/oauth2_handler.py +151 -0
  36. hyperpocket/auth/slack/oauth2_schema.py +40 -0
  37. hyperpocket/auth/slack/tests/__init__.py +0 -0
  38. hyperpocket/auth/slack/tests/test_oauth2_handler.py +32 -0
  39. hyperpocket/auth/slack/tests/test_token_handler.py +23 -0
  40. hyperpocket/auth/slack/token_context.py +14 -0
  41. hyperpocket/auth/slack/token_handler.py +64 -0
  42. hyperpocket/auth/slack/token_schema.py +9 -0
  43. hyperpocket/auth/tests/__init__.py +0 -0
  44. hyperpocket/auth/tests/test_google_oauth2_handler.py +147 -0
  45. hyperpocket/auth/tests/test_slack_oauth2_handler.py +147 -0
  46. hyperpocket/auth/tests/test_slack_token_handler.py +66 -0
  47. hyperpocket/cli/__init__.py +0 -0
  48. hyperpocket/cli/__main__.py +12 -0
  49. hyperpocket/cli/pull.py +18 -0
  50. hyperpocket/cli/sync.py +17 -0
  51. hyperpocket/config/__init__.py +9 -0
  52. hyperpocket/config/auth.py +36 -0
  53. hyperpocket/config/git.py +17 -0
  54. hyperpocket/config/logger.py +81 -0
  55. hyperpocket/config/session.py +35 -0
  56. hyperpocket/config/settings.py +62 -0
  57. hyperpocket/constants.py +0 -0
  58. hyperpocket/curated_tools.py +10 -0
  59. hyperpocket/external/__init__.py +7 -0
  60. hyperpocket/external/github_client.py +19 -0
  61. hyperpocket/futures/__init__.py +7 -0
  62. hyperpocket/futures/futurestore.py +48 -0
  63. hyperpocket/pocket_auth.py +344 -0
  64. hyperpocket/pocket_main.py +351 -0
  65. hyperpocket/prompts.py +15 -0
  66. hyperpocket/repository/__init__.py +5 -0
  67. hyperpocket/repository/lock.py +156 -0
  68. hyperpocket/repository/lockfile.py +56 -0
  69. hyperpocket/repository/repository.py +18 -0
  70. hyperpocket/server/__init__.py +3 -0
  71. hyperpocket/server/auth/__init__.py +15 -0
  72. hyperpocket/server/auth/calendly.py +16 -0
  73. hyperpocket/server/auth/github.py +25 -0
  74. hyperpocket/server/auth/google.py +16 -0
  75. hyperpocket/server/auth/linear.py +18 -0
  76. hyperpocket/server/auth/slack.py +28 -0
  77. hyperpocket/server/auth/token.py +51 -0
  78. hyperpocket/server/proxy.py +63 -0
  79. hyperpocket/server/server.py +178 -0
  80. hyperpocket/server/tool/__init__.py +10 -0
  81. hyperpocket/server/tool/dto/__init__.py +0 -0
  82. hyperpocket/server/tool/dto/script.py +15 -0
  83. hyperpocket/server/tool/wasm.py +31 -0
  84. hyperpocket/session/README.KR.md +62 -0
  85. hyperpocket/session/README.md +61 -0
  86. hyperpocket/session/__init__.py +4 -0
  87. hyperpocket/session/in_memory.py +76 -0
  88. hyperpocket/session/interface.py +118 -0
  89. hyperpocket/session/redis.py +126 -0
  90. hyperpocket/session/tests/__init__.py +0 -0
  91. hyperpocket/session/tests/test_in_memory.py +145 -0
  92. hyperpocket/session/tests/test_redis.py +151 -0
  93. hyperpocket/tests/__init__.py +0 -0
  94. hyperpocket/tests/test_pocket.py +118 -0
  95. hyperpocket/tests/test_pocket_auth.py +982 -0
  96. hyperpocket/tool/README.KR.md +68 -0
  97. hyperpocket/tool/README.md +75 -0
  98. hyperpocket/tool/__init__.py +13 -0
  99. hyperpocket/tool/builtins/__init__.py +0 -0
  100. hyperpocket/tool/builtins/example/__init__.py +0 -0
  101. hyperpocket/tool/builtins/example/add_tool.py +18 -0
  102. hyperpocket/tool/function/README.KR.md +159 -0
  103. hyperpocket/tool/function/README.md +169 -0
  104. hyperpocket/tool/function/__init__.py +9 -0
  105. hyperpocket/tool/function/annotation.py +30 -0
  106. hyperpocket/tool/function/tool.py +87 -0
  107. hyperpocket/tool/tests/__init__.py +0 -0
  108. hyperpocket/tool/tests/test_function_tool.py +266 -0
  109. hyperpocket/tool/tool.py +106 -0
  110. hyperpocket/tool/wasm/README.KR.md +144 -0
  111. hyperpocket/tool/wasm/README.md +144 -0
  112. hyperpocket/tool/wasm/__init__.py +3 -0
  113. hyperpocket/tool/wasm/browser.py +63 -0
  114. hyperpocket/tool/wasm/invoker.py +41 -0
  115. hyperpocket/tool/wasm/script.py +82 -0
  116. hyperpocket/tool/wasm/templates/__init__.py +28 -0
  117. hyperpocket/tool/wasm/templates/node.py +87 -0
  118. hyperpocket/tool/wasm/templates/python.py +75 -0
  119. hyperpocket/tool/wasm/tool.py +147 -0
  120. hyperpocket/util/__init__.py +1 -0
  121. hyperpocket/util/extract_func_param_desc_from_docstring.py +97 -0
  122. hyperpocket/util/find_all_leaf_class_in_package.py +17 -0
  123. hyperpocket/util/find_all_subclass_in_package.py +29 -0
  124. hyperpocket/util/flatten_json_schema.py +45 -0
  125. hyperpocket/util/function_to_model.py +46 -0
  126. hyperpocket/util/get_objects_from_subpackage.py +28 -0
  127. hyperpocket/util/json_schema_to_model.py +69 -0
  128. hyperpocket-0.0.1.dist-info/METADATA +304 -0
  129. hyperpocket-0.0.1.dist-info/RECORD +131 -0
  130. hyperpocket-0.0.1.dist-info/WHEEL +4 -0
  131. hyperpocket-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,25 @@
1
+ from datetime import datetime, timedelta, timezone
2
+
3
+ from hyperpocket.auth.calendly.context import CalendlyAuthContext
4
+ from hyperpocket.auth.calendly.oauth2_schema import CalendlyOAuth2Response
5
+
6
+
7
+ class CalendlyOAuth2AuthContext(CalendlyAuthContext):
8
+ @classmethod
9
+ def from_calendly_oauth2_response(
10
+ cls, response: CalendlyOAuth2Response
11
+ ) -> "CalendlyOAuth2AuthContext":
12
+ description = f"Calendly OAuth2 Context logged in with {response.scope} scopes"
13
+ now = datetime.now(tz=timezone.utc)
14
+
15
+ if response.expires_in:
16
+ expires_at = now + timedelta(seconds=response.expires_in)
17
+ else:
18
+ expires_at = None
19
+
20
+ return cls(
21
+ access_token=response.access_token,
22
+ description=description,
23
+ expires_at=expires_at,
24
+ detail=response,
25
+ )
@@ -0,0 +1,146 @@
1
+ from typing import Optional
2
+ from urllib.parse import urlencode, urljoin
3
+
4
+ import httpx
5
+
6
+ from hyperpocket.auth.calendly.oauth2_context import CalendlyOAuth2AuthContext
7
+ from hyperpocket.auth.calendly.oauth2_schema import CalendlyOAuth2Request, CalendlyOAuth2Response
8
+ from hyperpocket.auth.context import AuthContext
9
+ from hyperpocket.auth.handler import AuthHandlerInterface, AuthProvider
10
+ from hyperpocket.config import config
11
+ from hyperpocket.futures import FutureStore
12
+
13
+
14
+ class CalendlyOAuth2AuthHandler(AuthHandlerInterface):
15
+ _CALENDLY_AUTH_URL = "https://auth.calendly.com/oauth/authorize"
16
+ _CALENDLY_TOKEN_URL = "https://auth.calendly.com/oauth/token"
17
+
18
+ name: str = "calendly-oauth2"
19
+ description: str = "This handler is used to authenticate users using Calendly OAuth."
20
+ scoped: bool = False
21
+
22
+ @staticmethod
23
+ def provider() -> AuthProvider:
24
+ return AuthProvider.CALENDLY
25
+
26
+ @staticmethod
27
+ def provider_default() -> bool:
28
+ return True
29
+
30
+ @staticmethod
31
+ def recommended_scopes() -> set[str]:
32
+ return set()
33
+
34
+ def prepare(
35
+ self,
36
+ auth_req: CalendlyOAuth2Request,
37
+ thread_id: str,
38
+ profile: str,
39
+ future_uid: str,
40
+ *args,
41
+ **kwargs,
42
+ ) -> str:
43
+ redirect_uri = urljoin(
44
+ config.public_base_url + "/",
45
+ f"{config.callback_url_rewrite_prefix}/auth/calendly/oauth2/callback",
46
+ )
47
+ auth_url = self._make_auth_url(auth_req, redirect_uri, future_uid)
48
+
49
+ FutureStore.create_future(
50
+ future_uid,
51
+ data={
52
+ "redirect_uri": redirect_uri,
53
+ "thread_id": thread_id,
54
+ "profile": profile,
55
+ },
56
+ )
57
+
58
+ return f"User needs to authenticate using the following URL: {auth_url}"
59
+
60
+ async def authenticate(
61
+ self, auth_req: CalendlyOAuth2Request, future_uid: str, *args, **kwargs
62
+ ) -> AuthContext:
63
+ future_data = FutureStore.get_future(future_uid)
64
+ auth_code = await future_data.future
65
+
66
+ async with httpx.AsyncClient() as client:
67
+ resp = await client.post(
68
+ url=self._CALENDLY_TOKEN_URL,
69
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
70
+ auth=(auth_req.client_id, auth_req.client_secret),
71
+ data={
72
+ "code": auth_code,
73
+ "client_id": auth_req.client_id,
74
+ "client_secret": auth_req.client_secret,
75
+ "redirect_uri": future_data.data["redirect_uri"],
76
+ "grant_type": "authorization_code",
77
+ },
78
+ )
79
+
80
+ if resp.status_code != 200:
81
+ raise Exception(f"failed to authenticate. status_code : {resp.status_code}")
82
+ result = resp.json()
83
+
84
+ auth_response = CalendlyOAuth2Response(
85
+ access_token=result["access_token"],
86
+ token_type=result["token_type"],
87
+ scope=result["scope"],
88
+ refresh_token=(
89
+ result["refresh_token"] if "refresh_token" in result else None
90
+ ),
91
+ expires_in=(
92
+ result["refresh_token_expires_in"]
93
+ if "refresh_token_expires_in" in result
94
+ else None
95
+ ),
96
+ )
97
+ return CalendlyOAuth2AuthContext.from_calendly_oauth2_response(auth_response)
98
+
99
+ async def refresh(
100
+ self, auth_req: CalendlyOAuth2Request, context: AuthContext, *args, **kwargs
101
+ ) -> AuthContext:
102
+ last_oauth2_resp: CalendlyOAuth2Response = context.detail
103
+ refresh_token = last_oauth2_resp.refresh_token
104
+ if refresh_token is None:
105
+ raise Exception(
106
+ f"refresh token is None. last_oauth2_resp: {last_oauth2_resp}"
107
+ )
108
+
109
+ async with httpx.AsyncClient() as client:
110
+ resp = await client.post(
111
+ url=self._CALENDLY_TOKEN_URL,
112
+ data={
113
+ "client_id": auth_req.client_id,
114
+ "client_secret": auth_req.client_secret,
115
+ "refresh_token": refresh_token,
116
+ "grant_type": "refresh_token",
117
+ },
118
+ )
119
+
120
+ if resp.status_code != 200:
121
+ raise Exception(
122
+ f"failed to authenticate. status_code : {resp.status_code}"
123
+ )
124
+
125
+ resp_json = resp.json()
126
+ resp_json["refresh_token"] = refresh_token
127
+ response = CalendlyOAuth2Response(**resp_json)
128
+ return CalendlyOAuth2AuthContext.from_calendly_oauth2_response(response)
129
+
130
+ def _make_auth_url(self, auth_req: CalendlyOAuth2Request, redirect_uri: str, state: str):
131
+ params = {
132
+ "client_id": auth_req.client_id,
133
+ "redirect_uri": redirect_uri,
134
+ "response_type": "code",
135
+ "state": state,
136
+ }
137
+ return f"{self._CALENDLY_AUTH_URL}?{urlencode(params)}"
138
+
139
+ def make_request(
140
+ self, auth_scopes: Optional[list[str]] = None, **kwargs
141
+ ) -> CalendlyOAuth2Request:
142
+ return CalendlyOAuth2Request(
143
+ auth_scopes=auth_scopes,
144
+ client_id=config.auth.calendly.client_id,
145
+ client_secret=config.auth.calendly.client_secret,
146
+ )
@@ -0,0 +1,16 @@
1
+ from typing import Optional
2
+ from hyperpocket.auth.handler import AuthenticateRequest
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class CalendlyOAuth2Request(AuthenticateRequest):
7
+ client_id: str
8
+ client_secret: str
9
+
10
+
11
+ class CalendlyOAuth2Response(BaseModel):
12
+ access_token: str
13
+ expires_in: Optional[int]
14
+ refresh_token: Optional[str]
15
+ scope: str
16
+ token_type: str
@@ -0,0 +1,38 @@
1
+ from abc import ABC, abstractmethod
2
+ from datetime import datetime
3
+ from typing import Optional, Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class AuthContext(BaseModel, ABC):
9
+ """
10
+ This class is used to define the interface of the authentication model.
11
+ """
12
+ access_token: str = Field(description="user's access token")
13
+ description: str = Field(description="description of this authentication context")
14
+ expires_at: Optional[datetime] = Field(description="expiration datetime")
15
+ detail: Optional[Any] = Field(default=None, description="detailed information")
16
+
17
+ @abstractmethod
18
+ def to_dict(self) -> dict[str, str]:
19
+ """
20
+ This method is used to convert the authentication context to a dictionary.
21
+
22
+ Returns:
23
+ dict[str, str]: The dictionary representation of the authentication context.
24
+ """
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def to_profiled_dict(self, profile: str) -> dict[str, str]:
29
+ """
30
+ This method is used to convert the authentication context to a profiled dictionary.
31
+
32
+ Args:
33
+ profile (str): The profile name.
34
+
35
+ Returns:
36
+ dict[str, str]: The profiled dictionary representation of the authentication context.
37
+ """
38
+ raise NotImplementedError
File without changes
@@ -0,0 +1,13 @@
1
+ from hyperpocket.auth.context import AuthContext
2
+
3
+
4
+ class GitHubAuthContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "GITHUB_TOKEN"
6
+
7
+ def to_dict(self) -> dict[str, str]:
8
+ return {self._ACCESS_TOKEN_KEY: self.access_token}
9
+
10
+ def to_profiled_dict(self, profile: str) -> dict[str, str]:
11
+ return {
12
+ f"{profile.upper()}_{self._ACCESS_TOKEN_KEY}": self.access_token,
13
+ }
@@ -0,0 +1,25 @@
1
+ from datetime import datetime, timedelta, timezone
2
+
3
+ from hyperpocket.auth.github.context import GitHubAuthContext
4
+ from hyperpocket.auth.github.oauth2_schema import GitHubOAuth2Response
5
+
6
+
7
+ class GitHubOAuth2AuthContext(GitHubAuthContext):
8
+ @classmethod
9
+ def from_github_oauth2_response(
10
+ cls, response: GitHubOAuth2Response
11
+ ) -> "GitHubOAuth2AuthContext":
12
+ description = f"Github OAuth2 Context logged in with {response.scope} scopes"
13
+ now = datetime.now(tz=timezone.utc)
14
+
15
+ if response.expires_in:
16
+ expires_at = now + timedelta(seconds=response.expires_in)
17
+ else:
18
+ expires_at = None
19
+
20
+ return cls(
21
+ access_token=response.access_token,
22
+ description=description,
23
+ expires_at=expires_at,
24
+ detail=response,
25
+ )
@@ -0,0 +1,143 @@
1
+ from typing import Optional
2
+ from urllib.parse import parse_qs, urlencode, urljoin
3
+
4
+ import httpx
5
+
6
+ from hyperpocket.auth.context import AuthContext
7
+ from hyperpocket.auth.github.oauth2_context import GitHubOAuth2AuthContext
8
+ from hyperpocket.auth.github.oauth2_schema import GitHubOAuth2Request, GitHubOAuth2Response
9
+ from hyperpocket.auth.handler import AuthHandlerInterface, AuthProvider
10
+ from hyperpocket.config import config
11
+ from hyperpocket.futures import FutureStore
12
+
13
+
14
+ class GitHubOAuth2AuthHandler(AuthHandlerInterface):
15
+ _GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize"
16
+ _GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"
17
+
18
+ name: str = "github-oauth2"
19
+ description: str = "This handler is used to authenticate users using Github OAuth."
20
+ scoped: bool = True
21
+
22
+ @staticmethod
23
+ def provider() -> AuthProvider:
24
+ return AuthProvider.GITHUB
25
+
26
+ @staticmethod
27
+ def provider_default() -> bool:
28
+ return True
29
+
30
+ @staticmethod
31
+ def recommended_scopes() -> set[str]:
32
+ return {"repo"}
33
+
34
+ def prepare(
35
+ self,
36
+ auth_req: GitHubOAuth2Request,
37
+ thread_id: str,
38
+ profile: str,
39
+ future_uid: str,
40
+ *args,
41
+ **kwargs,
42
+ ) -> str:
43
+ redirect_uri = urljoin(
44
+ config.public_base_url + "/",
45
+ f"{config.callback_url_rewrite_prefix}/auth/github/oauth2/callback",
46
+ )
47
+ auth_url = self._make_auth_url(auth_req, redirect_uri, future_uid)
48
+
49
+ FutureStore.create_future(
50
+ future_uid,
51
+ data={
52
+ "redirect_uri": redirect_uri,
53
+ "thread_id": thread_id,
54
+ "profile": profile,
55
+ },
56
+ )
57
+
58
+ return f"User needs to authenticate using the following URL: {auth_url}"
59
+
60
+ async def authenticate(
61
+ self, auth_req: GitHubOAuth2Request, future_uid: str, *args, **kwargs
62
+ ) -> AuthContext:
63
+ future_data = FutureStore.get_future( future_uid)
64
+ auth_code = await future_data.future
65
+
66
+ async with httpx.AsyncClient() as client:
67
+ resp = await client.post(
68
+ url=self._GITHUB_TOKEN_URL,
69
+ data={
70
+ "code": auth_code,
71
+ "client_id": auth_req.client_id,
72
+ "client_secret": auth_req.client_secret,
73
+ "redirect_uri": future_data.data["redirect_uri"],
74
+ },
75
+ )
76
+
77
+ if resp.status_code != 200:
78
+ raise Exception(f"failed to authenticate. status_code : {resp.status_code}")
79
+
80
+ result = parse_qs(resp.text)
81
+ auth_response = GitHubOAuth2Response(
82
+ access_token=result["access_token"][0],
83
+ token_type=result["token_type"][0],
84
+ scope=result["scope"][0],
85
+ refresh_token=(
86
+ result["refresh_token"][0] if "refresh_token" in result else None
87
+ ),
88
+ expires_in=(
89
+ result["refresh_token_expires_in"][0]
90
+ if "refresh_token_expires_in" in result
91
+ else None
92
+ ),
93
+ )
94
+ return GitHubOAuth2AuthContext.from_github_oauth2_response(auth_response)
95
+
96
+ async def refresh(
97
+ self, auth_req: GitHubOAuth2Request, context: AuthContext, *args, **kwargs
98
+ ) -> AuthContext:
99
+ last_oauth2_resp: GitHubOAuth2Response = context.detail
100
+ refresh_token = last_oauth2_resp.refresh_token
101
+ if refresh_token is None:
102
+ raise Exception(
103
+ f"refresh token is None. last_oauth2_resp: {last_oauth2_resp}"
104
+ )
105
+
106
+ async with httpx.AsyncClient() as client:
107
+ resp = await client.post(
108
+ url=self._GITHUB_TOKEN_URL,
109
+ data={
110
+ "client_id": auth_req.client_id,
111
+ "client_secret": auth_req.client_secret,
112
+ "refresh_token": refresh_token,
113
+ "grant_type": "refresh_token",
114
+ },
115
+ )
116
+
117
+ if resp.status_code != 200:
118
+ raise Exception(
119
+ f"failed to authenticate. status_code : {resp.status_code}"
120
+ )
121
+
122
+ resp_json = resp.json()
123
+ resp_json["refresh_token"] = refresh_token
124
+ response = GitHubOAuth2Response(**resp_json)
125
+ return GitHubOAuth2AuthContext.from_github_oauth2_response(response)
126
+
127
+ def _make_auth_url(self, auth_req: GitHubOAuth2Request, redirect_uri: str, state: str):
128
+ params = {
129
+ "client_id": auth_req.client_id,
130
+ "redirect_uri": redirect_uri,
131
+ "scope": ",".join(auth_req.auth_scopes),
132
+ "state": state,
133
+ }
134
+ return f"{self._GITHUB_AUTH_URL}?{urlencode(params)}"
135
+
136
+ def make_request(
137
+ self, auth_scopes: Optional[list[str]] = None, **kwargs
138
+ ) -> GitHubOAuth2Request:
139
+ return GitHubOAuth2Request(
140
+ auth_scopes=auth_scopes,
141
+ client_id=config.auth.github.client_id,
142
+ client_secret=config.auth.github.client_secret,
143
+ )
@@ -0,0 +1,16 @@
1
+ from typing import Optional
2
+
3
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
4
+
5
+
6
+ class GitHubOAuth2Request(AuthenticateRequest):
7
+ client_id: str
8
+ client_secret: str
9
+
10
+
11
+ class GitHubOAuth2Response(AuthenticateResponse):
12
+ access_token: str
13
+ expires_in: Optional[int]
14
+ refresh_token: Optional[str]
15
+ scope: str
16
+ token_type: str
@@ -0,0 +1,12 @@
1
+ from hyperpocket.auth.github.context import GitHubAuthContext
2
+ from hyperpocket.auth.github.token_schema import GitHubTokenResponse
3
+
4
+
5
+ class GitHubTokenAuthContext(GitHubAuthContext):
6
+ @classmethod
7
+ def from_github_token_response(cls, response: GitHubTokenResponse):
8
+ description = "GitHub Token Context logged in"
9
+
10
+ return cls(
11
+ access_token=response.access_token, description=description, expires_at=None
12
+ )
@@ -0,0 +1,79 @@
1
+ from typing import Optional
2
+ from urllib.parse import urljoin, urlencode
3
+
4
+ from hyperpocket.auth import AuthProvider, AuthHandlerInterface
5
+ from hyperpocket.auth.context import AuthContext
6
+ from hyperpocket.auth.github.token_context import GitHubTokenAuthContext
7
+ from hyperpocket.auth.github.token_schema import GitHubTokenRequest, GitHubTokenResponse
8
+ from hyperpocket.auth.schema import AuthenticateRequest
9
+ from hyperpocket.config import config
10
+ from hyperpocket.futures import FutureStore
11
+
12
+
13
+ class GitHubTokenAuthHandler(AuthHandlerInterface):
14
+ name: str = "github-token"
15
+ description: str = (
16
+ "This handler is used to authenticate users using the GitHub token."
17
+ )
18
+ scoped: bool = False
19
+
20
+ _TOKEN_URL: str = urljoin(config.public_base_url + "/", f"{config.callback_url_rewrite_prefix}/auth/token")
21
+
22
+ @staticmethod
23
+ def provider() -> AuthProvider:
24
+ return AuthProvider.GITHUB
25
+
26
+ @staticmethod
27
+ def recommended_scopes() -> set[str]:
28
+ return set()
29
+
30
+ def prepare(
31
+ self,
32
+ auth_req: AuthenticateRequest,
33
+ thread_id: str,
34
+ profile: str,
35
+ future_uid: str,
36
+ *args,
37
+ **kwargs,
38
+ ) -> str:
39
+ redirect_uri = urljoin(
40
+ config.public_base_url + "/",
41
+ f"{config.callback_url_rewrite_prefix}/auth/github/token/callback",
42
+ )
43
+ auth_url = self._make_auth_url(auth_req=auth_req, redirect_uri=redirect_uri, state=future_uid)
44
+ FutureStore.create_future(
45
+ future_uid,
46
+ data={
47
+ "redirect_uri": redirect_uri,
48
+ "thread_id": thread_id,
49
+ "profile": profile,
50
+ },
51
+ )
52
+
53
+ return f"User needs to authenticate using the following URL: {auth_url}"
54
+
55
+ async def authenticate(
56
+ self, auth_req: GitHubTokenRequest, future_uid: str, *args, **kwargs
57
+ ) -> GitHubTokenAuthContext:
58
+ future_data = FutureStore.get_future(future_uid)
59
+ access_token = await future_data.future
60
+
61
+ response = GitHubTokenResponse(access_token=access_token)
62
+ context = GitHubTokenAuthContext.from_github_token_response(response)
63
+
64
+ return context
65
+
66
+ async def refresh(
67
+ self, auth_req: GitHubTokenRequest, context: AuthContext, *args, **kwargs
68
+ ) -> AuthContext:
69
+ raise Exception("GitHub token doesn't support refresh")
70
+
71
+ def _make_auth_url(self, auth_req: GitHubTokenRequest, redirect_uri: str, state: str):
72
+ params = {
73
+ "redirect_uri": redirect_uri,
74
+ "state": state
75
+ }
76
+ return f"{self._TOKEN_URL}?{urlencode(params)}"
77
+
78
+ def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> GitHubTokenRequest:
79
+ return GitHubTokenRequest()
@@ -0,0 +1,9 @@
1
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
2
+
3
+
4
+ class GitHubTokenRequest(AuthenticateRequest):
5
+ pass
6
+
7
+
8
+ class GitHubTokenResponse(AuthenticateResponse):
9
+ access_token: str
File without changes
@@ -0,0 +1,15 @@
1
+ from hyperpocket.auth.context import AuthContext
2
+
3
+
4
+ class GoogleAuthContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "GOOGLE_TOKEN"
6
+
7
+ def to_dict(self) -> dict[str, str]:
8
+ return {
9
+ self._ACCESS_TOKEN_KEY: self.access_token
10
+ }
11
+
12
+ def to_profiled_dict(self, profile: str) -> dict[str, str]:
13
+ return {
14
+ f"{profile.upper()}_{self._ACCESS_TOKEN_KEY}": self.access_token,
15
+ }
@@ -0,0 +1,31 @@
1
+ from datetime import datetime, timedelta, timezone
2
+ from typing import Optional
3
+
4
+ from pydantic import Field
5
+
6
+ from hyperpocket.auth.google.context import GoogleAuthContext
7
+ from hyperpocket.auth.google.oauth2_schema import GoogleOAuth2Response
8
+
9
+
10
+ class GoogleOAuth2AuthContext(GoogleAuthContext):
11
+ refresh_token: Optional[str] = Field(default=None, description="Refresh token")
12
+
13
+ @classmethod
14
+ def from_google_oauth2_response(
15
+ cls, response: GoogleOAuth2Response
16
+ ) -> "GoogleOAuth2AuthContext":
17
+ description = f"Google OAuth2 Context logged in with {response.scope} scopes"
18
+ now = datetime.now(tz=timezone.utc)
19
+
20
+ if response.expires_in:
21
+ expires_at = now + timedelta(seconds=response.expires_in)
22
+ else:
23
+ expires_at = None
24
+
25
+ return cls(
26
+ access_token=response.access_token,
27
+ refresh_token=response.refresh_token,
28
+ description=description,
29
+ expires_at=expires_at,
30
+ detail=response,
31
+ )