diff --git a/.github/workflows/docker-server-build.yaml b/.github/workflows/docker-server-build.yaml
index 7e477eae1..09d2a5ec8 100644
--- a/.github/workflows/docker-server-build.yaml
+++ b/.github/workflows/docker-server-build.yaml
@@ -13,7 +13,7 @@ concurrency:
jobs:
build-server:
name: Docker Build and Push Server
- runs-on: ubuntu-latest
+ runs-on: ubicloud-standard-8
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/docs/public/stack-auth-cli-template.py b/docs/public/stack-auth-cli-template.py
new file mode 100644
index 000000000..126d19b35
--- /dev/null
+++ b/docs/public/stack-auth-cli-template.py
@@ -0,0 +1,56 @@
+import time
+import requests
+import webbrowser
+import urllib.parse
+
+def prompt_cli_login(
+ *,
+ base_url: str = "https://api.stack-auth.com",
+ app_url: str,
+ project_id: str,
+ publishable_client_key: str,
+):
+ if not app_url:
+ raise Exception("app_url is required and must be set to the URL of the app you're authenticating with")
+ if not project_id:
+ raise Exception("project_id is required")
+ if not publishable_client_key:
+ raise Exception("publishable_client_key is required")
+
+ def post(endpoint, json):
+ return requests.request(
+ 'POST',
+ f'{base_url}{endpoint}',
+ headers={
+ 'Content-Type': 'application/json',
+ 'x-stack-project-id': project_id,
+ 'x-stack-access-type': 'client',
+ 'x-stack-publishable-client-key': publishable_client_key,
+ },
+ json=json,
+ )
+
+ # Step 1: Initiate the CLI auth process
+ init = post('/api/v1/auth/cli', {
+ 'expires_in_millis': 10 * 60 * 1000,
+ })
+ if init.status_code != 200:
+ raise Exception(f"Failed to initiate CLI auth: {init.status_code} {init.text}")
+ polling_code = init.json()['polling_code']
+ login_code = init.json()['login_code']
+
+ # Step 2: Open the browser for the user to authenticate
+ url = f'{app_url}/handler/cli-auth-confirm?login_code={urllib.parse.quote(login_code)}'
+ print(f"Opening browser to authenticate. If it doesn't open automatically, please visit:\n{url}")
+ webbrowser.open(url)
+
+ # Step 3: Retrieve the token
+ while True:
+ status = post('/api/v1/auth/cli/poll', {
+ 'polling_code': polling_code,
+ })
+ if status.status_code != 200 and status.status_code != 201:
+ raise Exception(f"Failed to get CLI auth status: {status.status_code} {status.text}")
+ if status.json()['status'] == 'success':
+ return status.json()['refresh_token']
+ time.sleep(2)
diff --git a/docs/src/components/layouts/api/api-sidebar.tsx b/docs/src/components/layouts/api/api-sidebar.tsx
index d2c21fddd..4d06a6f22 100644
--- a/docs/src/components/layouts/api/api-sidebar.tsx
+++ b/docs/src/components/layouts/api/api-sidebar.tsx
@@ -247,56 +247,74 @@ export function ApiSidebarContent({ pages = [] }: { pages?: PageData[] }) {
Overview
- {Object.entries(organizedPages).map(([sectionKey, section]) => (
-
-
{section.title}
+ {Object.entries(organizedPages)
+ .filter(([sectionKey]) => sectionKey !== 'admin') // Hide admin section from sidebar
+ .sort(([aKey], [bKey]) => {
+ // Define the desired order of sections
+ const sectionOrder = ['client', 'server', 'webhooks'];
+ const aIndex = sectionOrder.indexOf(aKey);
+ const bIndex = sectionOrder.indexOf(bKey);
+ // If both sections are in our defined order, sort by that order
+ if (aIndex !== -1 && bIndex !== -1) {
+ return aIndex - bIndex;
+ }
+ // If only one is in our defined order, prioritize it
+ if (aIndex !== -1) return -1;
+ if (bIndex !== -1) return 1;
+ // If neither is in our defined order, sort alphabetically
+ //eslint-disable-next-line
+ return aKey.localeCompare(bKey);
+ })
+ .map(([sectionKey, section]) => (
+
+
{section.title}
- {/* Section-level pages */}
- {section.pages.length > 0 && section.pages.map((page: PageData) => (
-
- {page.data.title || formatTitle(page.slugs[page.slugs.length - 1])}
-
- ))}
+ {/* Section-level pages */}
+ {section.pages.length > 0 && section.pages.map((page: PageData) => (
+
+ {page.data.title || formatTitle(page.slugs[page.slugs.length - 1])}
+
+ ))}
- {/* Grouped pages */}
- {Object.entries(section.groups).map(([groupKey, group]: [string, OrganizedGroup]) => (
-
- {group.pages.map((page: PageData) => {
- const method = getHttpMethod(page);
- const title = page.data.title || formatTitle(page.slugs[page.slugs.length - 1]);
+ {/* Grouped pages */}
+ {Object.entries(section.groups).map(([groupKey, group]: [string, OrganizedGroup]) => (
+
+ {group.pages.map((page: PageData) => {
+ const method = getHttpMethod(page);
+ const title = page.data.title || formatTitle(page.slugs[page.slugs.length - 1]);
+
+ // Special handling for webhooks (EVENT badge instead of HTTP method)
+ if (sectionKey === 'webhooks') {
+ return (
+
+
+
+ EVENT
+
+ {title}
+
+
+ );
+ }
- // Special handling for webhooks (EVENT badge instead of HTTP method)
- if (sectionKey === 'webhooks') {
return (
-
-
-
- EVENT
-
- {title}
-
+
+ {title}
);
- }
-
- return (
-
- {title}
-
- );
- })}
-
- ))}
-
- ))}
+ })}
+
+ ))}
+
+ ))}
diff --git a/docs/templates/others/cli-authentication.mdx b/docs/templates/others/cli-authentication.mdx
index a671df140..e95632713 100644
--- a/docs/templates/others/cli-authentication.mdx
+++ b/docs/templates/others/cli-authentication.mdx
@@ -5,7 +5,7 @@ description: How to authenticate a command line application using Stack Auth
If you're building a command line application that runs in a terminal, you can use Stack Auth to let your users log in to their accounts.
-To do so, we provide a Python template that you can use as a starting point. [Download it here](https://github.com/stack-auth/stack-auth/tree/main/docs/examples/stack_auth_cli_template.py) and copy it into your project, for example:
+To do so, we provide a Python template that you can use as a starting point. [Download it here](https://github.com/stack-auth/stack-auth/tree/main/docs/public/stack_auth_cli_template.py) and copy it into your project, for example:
```py
└─ my-python-app