Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix token expiration and improve apps #662

Merged
merged 3 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ svc = await get_remote_service("http://localhost:9527/ws-user-scintillating-lawy
Include the following script in your HTML file to load the `hypha-rpc` client:

```html
<script src="https://cdn.jsdelivr.net/npm/[email protected].26/dist/hypha-rpc-websocket.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected].27/dist/hypha-rpc-websocket.min.js"></script>
```

Use the following code in JavaScript to connect to the server and access an existing service:
Expand Down
10 changes: 5 additions & 5 deletions docs/migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To connect to the server, instead of installing the `imjoy-rpc` module, you will
pip install -U hypha-rpc # new install
```

We also changed our versioning strategy, we use the same version number for the server and client, so it's easier to match the client and server versions. For example, `hypha-rpc` version `0.20.26` is compatible with Hypha server version `0.20.26`.
We also changed our versioning strategy, we use the same version number for the server and client, so it's easier to match the client and server versions. For example, `hypha-rpc` version `0.20.27` is compatible with Hypha server version `0.20.27`.

#### 2. Change the imports to use `hypha-rpc`

Expand Down Expand Up @@ -128,10 +128,10 @@ loop.run_forever()
To connect to the server, instead of using the `imjoy-rpc` module, you will need to use the `hypha-rpc` module. The `hypha-rpc` module is a standalone module that provides the RPC connection to the Hypha server. You can include it in your HTML using a script tag:

```html
<script src="https://cdn.jsdelivr.net/npm/[email protected].26/dist/hypha-rpc-websocket.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected].27/dist/hypha-rpc-websocket.min.js"></script>
```

We also changed our versioning strategy, we use the same version number for the server and client, so it's easier to match the client and server versions. For example, `hypha-rpc` version `0.20.26` is compatible with Hypha server version `0.20.26`.
We also changed our versioning strategy, we use the same version number for the server and client, so it's easier to match the client and server versions. For example, `hypha-rpc` version `0.20.27` is compatible with Hypha server version `0.20.27`.

#### 2. Change the connection method and use camelCase for service function names

Expand All @@ -149,7 +149,7 @@ Here is a suggested list of search and replace operations to update your code:
Here is an example of how the updated code might look:

```html
<script src="https://cdn.jsdelivr.net/npm/[email protected].26/dist/hypha-rpc-websocket.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected].27/dist/hypha-rpc-websocket.min.js"></script>
<script>
async function main(){
const server = await hyphaWebsocketClient.connectToServer({"server_url": "https://hypha.amun.ai"});
Expand Down Expand Up @@ -197,7 +197,7 @@ We created a tutorial to introduce this new feature: [service type annotation](.
Here is a quick example in JavaScript:

```html
<script src="https://cdn.jsdelivr.net/npm/[email protected].26/dist/hypha-rpc-websocket.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected].27/dist/hypha-rpc-websocket.min.js"></script>

<script>
async function main(){
Expand Down
2 changes: 1 addition & 1 deletion docs/service-type-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ if __name__ == "__main__":
**JavaScript Client: Service Usage**

```html
<script src="https://cdn.jsdelivr.net/npm/[email protected].26/dist/hypha-rpc-websocket.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected].27/dist/hypha-rpc-websocket.min.js"></script>
<script>
async function main() {
const server = await hyphaWebsocketClient.connectToServer({"server_url": "https://hypha.amun.ai"});
Expand Down
24 changes: 18 additions & 6 deletions helm-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@ Make sure you have the following installed:

## Usage

First, we need to create secrets for hypha by copy the file `.env-template` to `.env`, then change the values inside .env file.
First, clone this repository:
```bash
git clone https://amun-ai.github.com/hypha
cd hypha/helm-chart
```

Now you need to create a namespace for Hypha:
```bash
kubectl create namespace hypha
```

After that, run the bash command to apply these secrets to the cluster:
Then, run the bash command to create a `JWT_SECRET` apply these secrets to the cluster:
```bash
sh apply-secrets.sh
export JWT_SECRET=abcde123 # change this to your own secret
kubectl create secret generic hypha-secrets \
--from-literal=JWT_SECRET=$JWT_SECRET \
--dry-run=client -o yaml | kubectl apply --namespace=hypha -f -
```

Verify that the secret `hypha-secrets` has been created:
Expand All @@ -26,16 +38,16 @@ Next, let's check the values in `values.yaml` and make sure they are correct. Im


Finally, we can install the helm chart:
```
```bash
helm install hypha-server ./hypha-server --namespace=hypha
```

If you make changes to the chart, you can upgrade the chart:
```
```bash
helm upgrade hypha-server ./hypha-server --namespace=hypha
```

To uninstall the chart:
```
```bash
helm uninstall hypha-server --namespace=hypha
```
2 changes: 1 addition & 1 deletion helm-chart/hypha-server/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ image:
repository: ghcr.io/amun-ai/hypha
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "0.20.26"
tag: "0.20.27"

imagePullSecrets: []
nameOverride: ""
Expand Down
2 changes: 1 addition & 1 deletion hypha/VERSION
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "0.20.26"
"version": "0.20.27"
}
34 changes: 24 additions & 10 deletions hypha/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ async def save_application(
app_id: str,
card: Card,
source: str,
entry_point: str,
attachments: Optional[Dict[str, Any]] = None,
):
"""Save an application to the workspace."""
Expand Down Expand Up @@ -159,7 +160,7 @@ async def save_file(key, content):
assert "ETag" in response

# Upload the source code
await save_file(f"{app_dir}/index.html", source)
await save_file(f"{app_dir}/{entry_point}", source)

if attachments:
card.attachments = card.attachments or {}
Expand Down Expand Up @@ -225,7 +226,8 @@ async def install(
"""Save a server app."""
if template is None:
if config:
template = config.get("type") + "-app.html"
config["entry_point"] = config.get("entry_point", "index.html")
template = config.get("type") + "." + config["entry_point"]
else:
template = "hypha"
if not workspace:
Expand Down Expand Up @@ -265,9 +267,12 @@ async def install(
try:
config = parse_imjoy_plugin(source)
config["source_hash"] = mhash
entry_point = config.get("entry_point", "index.html")
config["entry_point"] = entry_point
temp = self.jinja_env.get_template(
safe_join("apps", config["type"] + "-app.html")
safe_join("apps", config["type"] + "." + entry_point)
)

source = temp.render(
hypha_main_version=main_version,
hypha_rpc_websocket_mjs=self.public_base_url
Expand All @@ -282,14 +287,20 @@ async def install(
f"Failed to parse or compile the hypha app {mhash}: {source[:100]}...",
) from err
elif template:
assert (
"." in template
), f"Invalid template name: {template}, should be a file name with extension."
# extract the last dash separated part as the file name
temp = self.jinja_env.get_template(safe_join("apps", template))
default_config = {
"name": "Untitled Plugin",
"name": "Untitled App",
"version": "0.1.0",
"local_base_url": self.local_base_url,
}
default_config.update(config or {})
config = default_config
entry_point = config.get("entry_point", template)
config["entry_point"] = entry_point
source = temp.render(
hypha_main_version=main_version,
hypha_rpc_websocket_mjs=self.public_base_url
Expand All @@ -304,17 +315,18 @@ async def install(

app_id = f"{mhash}"

public_url = f"{self.public_base_url}/{workspace_info.name}/browser-apps/{app_id}/index.html"
public_url = f"{self.public_base_url}/{workspace_info.name}/server-apps/{app_id}/{entry_point}"
card_obj = convert_config_to_card(config, app_id, public_url)
card_obj.update(
{
"local_url": f"{self.local_base_url}/{workspace_info.name}/browser-apps/{app_id}/index.html",
"local_url": f"{self.local_base_url}/{workspace_info.name}/server-apps/{app_id}/{entry_point}",
"public_url": public_url,
}
)
card = Card.model_validate(card_obj)
assert card.entry_point, "Entry point not found in the card."
await self.save_application(
workspace_info.name, app_id, card, source, attachments
workspace_info.name, app_id, card, source, card.entry_point, attachments
)
async with self.store.get_workspace_interface(
workspace_info.name, user_info
Expand Down Expand Up @@ -426,10 +438,12 @@ async def start(
assert (
app_id in workspace_info.applications
), f"App {app_id} not found in workspace {workspace}, please install it first."

card = workspace_info.applications[app_id]
entry_point = card.entry_point
assert entry_point, f"Entry point not found for app {app_id}."
server_url = self.local_base_url
local_url = (
f"{self.local_base_url}/{workspace}/browser-apps/{app_id}/index.html?"
f"{self.local_base_url}/{workspace}/server-apps/{app_id}/{entry_point}?"
+ f"client_id={client_id}&workspace={workspace}"
+ f"&app_id={app_id}"
+ f"&server_url={server_url}"
Expand All @@ -439,7 +453,7 @@ async def start(
)
server_url = self.public_base_url
public_url = (
f"{self.public_base_url}/{workspace}/browser-apps/{app_id}/index.html?"
f"{self.public_base_url}/{workspace}/server-apps/{app_id}/{entry_point}?"
+ f"client_id={client_id}&workspace={workspace}"
+ f"&app_id={app_id}"
+ f"&server_url={server_url}"
Expand Down
1 change: 1 addition & 0 deletions hypha/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ class Card(BaseModel):
license: Optional[str] = None
git_repo: Optional[str] = None
source: Optional[str] = None
entry_point: Optional[str] = None
services: Optional[List[SerializeAsAny[ServiceInfo]]] = None

@classmethod
Expand Down
21 changes: 12 additions & 9 deletions hypha/core/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import logging
import ssl
import sys
import time
from calendar import timegm
import datetime
import traceback
from os import environ as env
from typing import List, Union, Dict
Expand Down Expand Up @@ -143,9 +144,10 @@ def valid_token(authorization: str):

def generate_anonymous_user(scope=None) -> UserInfo:
"""Generate user info for an anonymous user."""
iat = time.time()
current_time = datetime.datetime.now(datetime.timezone.utc)
user_id = random_id(readable=True)
expires_at = iat + 600
expires_at = current_time + datetime.timedelta(seconds=600)
expires_at = timegm(expires_at.utctimetuple())
return UserInfo(
id=user_id,
is_anonymous=True,
Expand Down Expand Up @@ -182,7 +184,7 @@ def parse_token(authorization: str):

def generate_presigned_token(
user_info: UserInfo,
expires_in: int = None,
expires_in: int,
):
"""Generate presigned tokens.

Expand All @@ -193,9 +195,10 @@ def generate_presigned_token(
email = user_info.email
# Inherit roles from parent
roles = user_info.roles
expires_in = expires_in or 10800
current_time = time.time()
expires_at = current_time + expires_in
assert expires_in > 0, "expires_in should be greater than 0"
current_time = datetime.datetime.now(datetime.timezone.utc)
expires_at = current_time + datetime.timedelta(seconds=expires_in)

token = jwt.encode(
{
"iss": AUTH0_ISSUER,
Expand All @@ -216,8 +219,8 @@ def generate_presigned_token(

def generate_reconnection_token(user_info: UserInfo, expires_in: int = 60):
"""Generate a token for reconnection."""
current_time = time.time()
expires_at = current_time + expires_in
current_time = datetime.datetime.now(datetime.timezone.utc)
expires_at = current_time + datetime.timedelta(seconds=expires_in)

ret = jwt.encode(
{
Expand Down
1 change: 1 addition & 0 deletions hypha/core/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ def create_rpc(
client_id=client_id,
default_context=default_context,
workspace=workspace,
server_base_url=self.public_base_url,
silent=silent,
)
rpc.register_codec(
Expand Down
4 changes: 3 additions & 1 deletion hypha/core/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ async def generate_token(
current_workspace=allowed_workspace,
extra_scopes=extra_scopes,
)
token = generate_presigned_token(user_info)
config.expires_in = config.expires_in or 3600
token = generate_presigned_token(user_info, config.expires_in)
return token

@schema_method
Expand Down Expand Up @@ -841,6 +842,7 @@ def _create_rpc(
workspace=workspace,
client_id=client_id,
default_context=default_context,
server_base_url=self._server_info["public_base_url"],
silent=silent,
)
rpc.register_codec(
Expand Down
2 changes: 1 addition & 1 deletion hypha/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ async def send_response(send_queue):
except KeyError:
return Response(status_code=404)

@router.get(norm_url("/{workspace}/browser-apps/{app_id}/{path:path}"))
@router.get(norm_url("/{workspace}/server-apps/{app_id}/{path:path}"))
async def get_browser_app_file(
workspace: str, app_id: str, path: str, token: str = None
) -> Response:
Expand Down
1 change: 1 addition & 0 deletions hypha/plugin_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def convert_config_to_card(plugin_config, plugin_id, source_url=None):
"env",
"passive",
"services",
"entry_point",
]
for field in fields:
if field in plugin_config:
Expand Down
22 changes: 22 additions & 0 deletions hypha/templates/hypha-core-app/hypha-app-iframe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>Hypha App</title>
<style>
body {
margin: 0;
padding: 10px;
overflow: hidden;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/hypha-rpc-websocket.min.js"></script>
<script>
hyphaWebsocketClient.setupLocalClient({enable_execution: true}).then((api)=>{
console.log("Hypha client is ready", api)
}).catch(console.error);
</script>
</head>
<body>
<pre>Initializing...</pre>
</body>
</html>
Loading
Loading