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,137 @@
1
+ from typing import Optional
2
+ from urllib.parse import urlencode, urljoin
3
+
4
+ import httpx
5
+
6
+ from hyperpocket.auth.context import AuthContext
7
+ from hyperpocket.auth.google.oauth2_context import GoogleOAuth2AuthContext
8
+ from hyperpocket.auth.google.oauth2_schema import GoogleOAuth2Request, GoogleOAuth2Response
9
+ from hyperpocket.auth.handler import AuthHandlerInterface, AuthProvider
10
+ from hyperpocket.config import config
11
+ from hyperpocket.futures import FutureStore
12
+
13
+
14
+ class GoogleOAuth2AuthHandler(AuthHandlerInterface):
15
+ _GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
16
+ _GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
17
+
18
+ name: str = "google-oauth2"
19
+ description: str = "This handler is used to authenticate users using Google OAuth."
20
+ scoped: bool = True
21
+
22
+ @staticmethod
23
+ def provider() -> AuthProvider:
24
+ return AuthProvider.GOOGLE
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: GoogleOAuth2Request,
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/google/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: GoogleOAuth2Request, 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._GOOGLE_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
+ "grant_type": "authorization_code",
75
+ },
76
+ )
77
+
78
+ if resp.status_code != 200:
79
+ raise Exception(f"failed to authenticate. status_code : {resp.status_code}")
80
+
81
+ resp_json = resp.json()
82
+ auth_response = GoogleOAuth2Response(**resp_json)
83
+ return GoogleOAuth2AuthContext.from_google_oauth2_response(auth_response)
84
+
85
+ async def refresh(
86
+ self, auth_req: GoogleOAuth2Request, context: AuthContext, *args, **kwargs
87
+ ) -> AuthContext:
88
+ google_context: GoogleOAuth2AuthContext = context
89
+ last_oauth2_resp: GoogleOAuth2Response = google_context.detail
90
+ refresh_token = google_context.refresh_token
91
+ if refresh_token is None:
92
+ raise Exception(
93
+ f"refresh token is None. last_oauth2_resp: {last_oauth2_resp}"
94
+ )
95
+
96
+ async with httpx.AsyncClient() as client:
97
+ resp = await client.post(
98
+ url=self._GOOGLE_TOKEN_URL,
99
+ data={
100
+ "client_id": auth_req.client_id,
101
+ "client_secret": auth_req.client_secret,
102
+ "refresh_token": refresh_token,
103
+ "grant_type": "refresh_token",
104
+ },
105
+ )
106
+
107
+ if resp.status_code != 200:
108
+ raise Exception(
109
+ f"failed to authenticate. status_code : {resp.status_code}"
110
+ )
111
+
112
+ resp_json = resp.json()
113
+ if "refresh_token" not in resp_json:
114
+ resp_json["refresh_token"] = refresh_token
115
+
116
+ response = GoogleOAuth2Response(**resp_json)
117
+ return GoogleOAuth2AuthContext.from_google_oauth2_response(response)
118
+
119
+ def _make_auth_url(self, auth_req: GoogleOAuth2Request, redirect_uri: str, state: str):
120
+ params = {
121
+ "client_id": auth_req.client_id,
122
+ "redirect_uri": redirect_uri,
123
+ "response_type": "code",
124
+ "scope": " ".join(auth_req.auth_scopes),
125
+ "access_type": "offline",
126
+ "state": state,
127
+ }
128
+ return f"{self._GOOGLE_AUTH_URL}?{urlencode(params)}"
129
+
130
+ def make_request(
131
+ self, auth_scopes: Optional[list[str]] = None, **kwargs
132
+ ) -> GoogleOAuth2Request:
133
+ return GoogleOAuth2Request(
134
+ auth_scopes=auth_scopes,
135
+ client_id=config.auth.google.client_id,
136
+ client_secret=config.auth.google.client_secret,
137
+ )
@@ -0,0 +1,18 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import Field
4
+
5
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
6
+
7
+
8
+ class GoogleOAuth2Request(AuthenticateRequest):
9
+ client_id: str
10
+ client_secret: str
11
+
12
+
13
+ class GoogleOAuth2Response(AuthenticateResponse):
14
+ access_token: str
15
+ expires_in: int
16
+ refresh_token: Optional[str] = Field(default=None)
17
+ scope: str
18
+ token_type: str
@@ -0,0 +1,171 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ from pydantic import Field
5
+
6
+ from hyperpocket.auth.context import AuthContext
7
+ from hyperpocket.auth.provider import AuthProvider
8
+ from hyperpocket.auth.schema import AuthenticateRequest
9
+
10
+
11
+ class AuthHandlerInterface(ABC):
12
+ name: str = Field(description="name of the authentication handler")
13
+ description: str = Field(description="description of the authentication handler")
14
+ scoped: bool = Field(description="Indicates whether the handler requires an auth_scope for access control")
15
+
16
+ @staticmethod
17
+ def provider() -> AuthProvider:
18
+ """
19
+ Returns the authentication provider enum.
20
+
21
+ This method is used to determine the appropriate authentication handler
22
+
23
+ based on the authentication provider.
24
+ """
25
+ raise NotImplementedError()
26
+
27
+ @staticmethod
28
+ def provider_default() -> bool:
29
+ """
30
+ Indicates whether this authentication handler is the default handler.
31
+
32
+ If no specific handler is designated, the default handler will be used.
33
+
34
+ Returns:
35
+ bool: True if this handler is the default, False otherwise.
36
+ """
37
+ return False
38
+
39
+ @staticmethod
40
+ def recommended_scopes() -> set[str]:
41
+ """
42
+ Returns the recommended authentication scopes.
43
+
44
+ If `use_recommended_scope` is set to True in the `AuthConfig`,
45
+
46
+ this method should return the proper recommended scopes. Otherwise,
47
+
48
+ it should return an empty set.
49
+
50
+ Returns:
51
+ set[str]: A set of recommended scopes, or an empty set if not applicable.
52
+
53
+ Examples:
54
+ Slack OAuth2 recommended_scopes::
55
+
56
+ def recommended_scopes() -> set[str]:
57
+ if config.auth.slack.use_recommended_scope:
58
+ recommended_scopes = {
59
+ "channels:history",
60
+ "channels:read",
61
+ "chat:write",
62
+ "groups:history",
63
+ "groups:read",
64
+ "im:history",
65
+ "mpim:history",
66
+ "reactions:read",
67
+ "reactions:write",
68
+ }
69
+ else:
70
+ recommended_scopes = {}
71
+ return recommended_scopes
72
+ """
73
+ raise NotImplementedError()
74
+
75
+ @abstractmethod
76
+ def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> AuthenticateRequest:
77
+ """
78
+ Make an AuthenticationRequest.
79
+
80
+ Usually, this method only requires `auth_scopes`.
81
+
82
+ If additional static information is needed (e.g., clientID, secretID),
83
+
84
+ retrieve it from the configuration.
85
+
86
+ Args:
87
+ auth_scopes (Optional[list[str]]): list of auth scopes
88
+
89
+ Returns:
90
+ AuthenticateRequest: A authentication request object with the necessary details.
91
+
92
+ Examples:
93
+ Create a Slack OAuth2 request::
94
+
95
+ def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> SlackOAuth2Request:
96
+ return SlackOAuth2Request(
97
+ auth_scopes=auth_scopes,
98
+ client_id=config.auth.slack.client_id,
99
+ client_secret=config.auth.slack.client_secret
100
+ )
101
+ """
102
+ raise NotImplementedError()
103
+
104
+ @abstractmethod
105
+ def prepare(self, auth_req: AuthenticateRequest, thread_id: str, profile: str,
106
+ future_uid: str, *args, **kwargs) -> str:
107
+ """
108
+ Performs preliminary tasks required for authentication.
109
+
110
+ This method typically performs the following actions:
111
+
112
+ - Creates a future to wait for user authentication completion during the authentication process.
113
+ - Issues an authentication URI that the user can access.
114
+
115
+ Args:
116
+ auth_req (AuthenticateRequest): The authentication request object.
117
+ thread_id (str): The thread ID.
118
+ profile (str): The profile name.
119
+ future_uid (str): A unique identifier for each future.
120
+
121
+ Returns:
122
+ str: The authentication URI that the user can access.
123
+ """
124
+ raise NotImplementedError()
125
+
126
+ @abstractmethod
127
+ async def authenticate(self, auth_req: AuthenticateRequest, future_uid: str, *args, **kwargs) -> AuthContext:
128
+ """
129
+ Performs the actual authentication process.
130
+
131
+ This function assumes that the user has completed the authentication during the `prepare` step,
132
+ and the associated future has been resolved. At this point, the result contains the required
133
+ values for authentication (e.g., an auth code).
134
+
135
+ Typically, this process involves:
136
+
137
+ - Accessing the resolved future to retrieve the necessary values for authentication.
138
+ - Performing the actual authentication using these values.
139
+ - Converting the returned response into an appropriate `AuthContext` object and returning it.
140
+
141
+ Args:
142
+ auth_req (AuthenticateRequest): The authentication request object.
143
+ future_uid (str): A unique identifier for the future, used to retrieve the correct
144
+ result issued during the `prepare` step.
145
+
146
+ Returns:
147
+ AuthContext: The authentication context object containing the authentication result.
148
+ """
149
+ raise NotImplementedError()
150
+
151
+ @abstractmethod
152
+ async def refresh(self, auth_req: AuthenticateRequest, context: AuthContext, *args, **kwargs) -> AuthContext:
153
+ """
154
+ Performs re-authentication for an expired session.
155
+
156
+ This method is optional and does not need to be implemented for handlers that do not require re-authentication.
157
+
158
+ Typically, the information needed for re-authentication (e.g., a refresh token) should be stored
159
+ within the `AuthContext` during the previous authentication step.
160
+
161
+ In the `refresh` step, this method accesses the necessary re-authentication details from the provided `context`,
162
+ performs the re-authentication, and returns an updated `AuthContext`.
163
+
164
+ Args:
165
+ auth_req (AuthenticateRequest): The authentication request object.
166
+ context (AuthContext): The current authentication context that it should contain data required for re-authentication.
167
+
168
+ Returns:
169
+ AuthContext: An updated authentication context object.
170
+ """
171
+ raise NotImplementedError()
File without changes
@@ -0,0 +1,15 @@
1
+ from hyperpocket.auth.context import AuthContext
2
+
3
+
4
+ class LinearAuthContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "LINEAR_API_KEY"
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,15 @@
1
+ from hyperpocket.auth.linear.context import LinearAuthContext
2
+ from hyperpocket.auth.linear.token_schema import LinearTokenResponse
3
+
4
+
5
+ class LinearTokenAuthContext(LinearAuthContext):
6
+
7
+ @classmethod
8
+ def from_linear_token_response(cls, response: LinearTokenResponse):
9
+ description = f'Linear Token Context logged in'
10
+
11
+ return cls(
12
+ access_token=response.access_token,
13
+ description=description,
14
+ expires_at=None
15
+ )
@@ -0,0 +1,68 @@
1
+ from typing import Optional
2
+ from urllib.parse import urljoin, urlencode
3
+
4
+ from hyperpocket.auth import AuthProvider
5
+ from hyperpocket.auth.context import AuthContext
6
+ from hyperpocket.auth.handler import AuthHandlerInterface
7
+ from hyperpocket.auth.linear.token_context import LinearTokenAuthContext
8
+ from hyperpocket.auth.linear.token_schema import LinearTokenResponse, LinearTokenRequest
9
+ from hyperpocket.config import config
10
+ from hyperpocket.futures import FutureStore
11
+
12
+
13
+ class LinearTokenAuthHandler(AuthHandlerInterface):
14
+ name: str = "linear-token"
15
+ description: str = "This handler is used to authenticate users using the Linear token."
16
+ scoped: bool = False
17
+
18
+ _TOKEN_URL: str = urljoin(config.public_base_url + "/", f"{config.callback_url_rewrite_prefix}/auth/token")
19
+
20
+ @staticmethod
21
+ def provider() -> AuthProvider:
22
+ return AuthProvider.LINEAR
23
+
24
+ @staticmethod
25
+ def provider_default() -> bool:
26
+ return True
27
+
28
+ @staticmethod
29
+ def recommended_scopes() -> set[str]:
30
+ return set()
31
+
32
+ def prepare(self, auth_req: LinearTokenRequest, thread_id: str, profile: str,
33
+ future_uid: str, *args, **kwargs) -> str:
34
+ redirect_uri = urljoin(
35
+ config.public_base_url + "/",
36
+ f"{config.callback_url_rewrite_prefix}/auth/linear/token/callback",
37
+ )
38
+ auth_url = self._make_auth_url(auth_req=auth_req, redirect_uri=redirect_uri, state=future_uid)
39
+ FutureStore.create_future(future_uid, data={
40
+ "redirect_uri": redirect_uri,
41
+ "thread_id": thread_id,
42
+ "profile": profile,
43
+ })
44
+
45
+ return f'User needs to authenticate using the following URL: {auth_url}'
46
+
47
+ async def authenticate(self, auth_req: LinearTokenResponse, future_uid: str, *args,
48
+ **kwargs) -> LinearTokenAuthContext:
49
+ future_data = FutureStore.get_future( future_uid)
50
+ access_token = await future_data.future
51
+
52
+ response = LinearTokenResponse(access_token=access_token)
53
+ context = LinearTokenAuthContext.from_linear_token_response(response)
54
+
55
+ return context
56
+
57
+ async def refresh(self, auth_req: LinearTokenRequest, context: AuthContext, *args, **kwargs) -> AuthContext:
58
+ raise Exception("Linear token doesn't support refresh")
59
+
60
+ def _make_auth_url(self, auth_req: LinearTokenRequest, redirect_uri: str, state: str):
61
+ params = {
62
+ "redirect_uri": redirect_uri,
63
+ "state": state,
64
+ }
65
+ return f"{self._TOKEN_URL}?{urlencode(params)}"
66
+
67
+ def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> LinearTokenRequest:
68
+ return LinearTokenRequest()
@@ -0,0 +1,9 @@
1
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
2
+
3
+
4
+ class LinearTokenRequest(AuthenticateRequest):
5
+ pass
6
+
7
+
8
+ class LinearTokenResponse(AuthenticateResponse):
9
+ access_token: str
@@ -0,0 +1,16 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AuthProvider(Enum):
5
+ SLACK = 'slack'
6
+ LINEAR = 'linear'
7
+ GITHUB = 'github'
8
+ GOOGLE = 'google'
9
+ CALENDLY = 'calendly'
10
+
11
+ @classmethod
12
+ def get_auth_provider(cls, auth_provider_name: str) -> "AuthProvider":
13
+ try:
14
+ return AuthProvider[auth_provider_name.upper()]
15
+ except KeyError:
16
+ raise ValueError(f"Invalid auth provider name. {auth_provider_name}")
@@ -0,0 +1,19 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class AuthenticateRequest(BaseModel):
7
+ """
8
+ This class is used to define the interface of the authentication request.
9
+ """
10
+ auth_scopes: Optional[list[str]] = Field(
11
+ default_factory=list,
12
+ description="authentication scopes. if the authentication handler is non scoped, it isn't needed")
13
+
14
+
15
+ class AuthenticateResponse(BaseModel):
16
+ """
17
+ This class is used to define the interface of the authentication response.
18
+ """
19
+ pass
File without changes
@@ -0,0 +1,15 @@
1
+ from hyperpocket.auth.context import AuthContext
2
+
3
+
4
+ class SlackAuthContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "SLACK_BOT_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,40 @@
1
+ from datetime import datetime, timedelta, timezone
2
+ from typing import Optional
3
+
4
+ from pydantic import Field
5
+
6
+ from hyperpocket.auth.slack.context import SlackAuthContext
7
+ from hyperpocket.auth.slack.oauth2_schema import SlackOAuth2Response
8
+
9
+
10
+ class SlackOAuth2AuthContext(SlackAuthContext):
11
+ refresh_token: Optional[str] = Field(default=None, description="refresh token")
12
+
13
+ @classmethod
14
+ def from_slack_oauth2_response(cls, response: SlackOAuth2Response):
15
+ description = f'Slack OAuth2 Context logged in as a user {response.authed_user.id}'
16
+ now = datetime.now(tz=timezone.utc)
17
+
18
+ # user token
19
+ if response.authed_user:
20
+ access_token = response.authed_user.access_token
21
+ refresh_token = response.authed_user.refresh_token
22
+ expires_in = response.authed_user.expires_in
23
+ # bot token
24
+ else:
25
+ access_token = response.access_token
26
+ refresh_token = response.refresh_token
27
+ expires_in = response.expires_in
28
+
29
+ if expires_in:
30
+ expires_at = now + timedelta(seconds=response.authed_user.expires_in)
31
+ else:
32
+ expires_at = None
33
+
34
+ return cls(
35
+ access_token=access_token,
36
+ refresh_token=refresh_token,
37
+ expires_at=expires_at,
38
+ description=description,
39
+ detail=response,
40
+ )