autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
108
skills/apodex-batch-reg/references/passwordless-auth-flow.md
Normal file
108
skills/apodex-batch-reg/references/passwordless-auth-flow.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Apodex Passwordless Auth Flow
|
||||
|
||||
> Discovered 2026-06-26. This is the CORRECT auth flow, NOT the now-dead Supabase flow.
|
||||
|
||||
## Overview
|
||||
|
||||
Apodex uses a passwordless auth system proxied through `www.apodex.ai`, NOT direct Supabase calls. The old Supabase project (`hgejqhubxceqppqkgqgx.supabase.co`) is dead — DNS fails everywhere.
|
||||
|
||||
## Endpoints
|
||||
|
||||
Both work from Python WITHOUT cookies, CSRF tokens, or special headers:
|
||||
|
||||
| Endpoint | Method | Returns |
|
||||
|----------|--------|---------|
|
||||
| `https://www.apodex.ai/api/auth/passwordless/send-code` | POST | 200 + `{"success":true,"message":"Login code sent..."}` |
|
||||
| `https://www.apodex.ai/api/auth/passwordless/verify-login` | POST | 200 + `{"success":true,"data":{"access_token":"eyJ...","user":{...}}}` |
|
||||
|
||||
## Full Flow (Python)
|
||||
|
||||
```python
|
||||
import requests
|
||||
import imaplib, ssl, re, time, email
|
||||
|
||||
EMAIL = '[REDACTED-EMAIL]'
|
||||
PASSWORD = '[REDACTED]'
|
||||
|
||||
# Step 1: Send OTP
|
||||
s = requests.Session()
|
||||
r = s.post('https://www.apodex.ai/api/auth/passwordless/send-code',
|
||||
json={'email': EMAIL}, timeout=15)
|
||||
assert r.status_code == 200, f'Send code failed: {r.status_code} {r.text}'
|
||||
|
||||
# Step 2: Get OTP from IMAP
|
||||
time.sleep(3)
|
||||
ctx = ssl.create_default_context()
|
||||
imap = imaplib.IMAP4_SSL('secureimap.t-online.de', 993, ssl_context=ctx, timeout=15)
|
||||
imap.login(EMAIL, PASSWORD)
|
||||
imap.select('INBOX')
|
||||
_, msgs = imap.search(None, 'FROM', 'apodex')
|
||||
_, data = imap.fetch(msgs[0].split()[-1], '(RFC822)')
|
||||
raw = data[0][1].decode('utf-8', errors='ignore')
|
||||
msg = email.message_from_string(raw)
|
||||
body = '' if msg.is_multipart() else msg.get_payload(decode=True).decode('utf-8', errors='ignore')
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == 'text/plain':
|
||||
body = part.get_payload(decode=True).decode('utf-8', errors='ignore')
|
||||
break
|
||||
otp = re.findall(r'\b\d{6}\b', body)[0]
|
||||
imap.logout()
|
||||
|
||||
# Step 3: Verify OTP
|
||||
r = s.post('https://www.apodex.ai/api/auth/passwordless/verify-login',
|
||||
json={'email': EMAIL, 'code': otp}, timeout=15)
|
||||
data = r.json()
|
||||
access_token = data['data']['access_token'] # Apodex JWT
|
||||
|
||||
# Step 4: Use token (limited)
|
||||
r = requests.get('https://www.apodex.ai/api/auth/get_user_info',
|
||||
headers={'Authorization': f'Bearer {access_token}'})
|
||||
# ✅ Works — returns user profile
|
||||
|
||||
# ❌ What does NOT work:
|
||||
r = requests.get('https://api.apodex.ai/v1/api-keys',
|
||||
headers={'Authorization': f'Bearer {access_token}'})
|
||||
# Returns 401 {"error":"invalid token"} — need Supabase JWT instead
|
||||
|
||||
# But the existing sk- keys DO work:
|
||||
r = requests.get('https://api.apodex.ai/v1/models',
|
||||
headers={'Authorization': 'Bearer [REDACTED-KEY]='})
|
||||
# ✅ Returns model list
|
||||
```
|
||||
|
||||
## Token Comparison
|
||||
|
||||
```
|
||||
Apodex JWT (from verify-login):
|
||||
Header: {"alg":"HS256","typ":"JWT"}
|
||||
Payload: {"user_id":"email_xxx","exp":1785xxxxxx,"session_id":"uuid"}
|
||||
→ Works for: www.apodex.ai/api/auth/*
|
||||
→ Fails for: api.apodex.ai/v1/api-keys
|
||||
|
||||
Supabase JWT (from Supabase auth, now DEAD):
|
||||
Header: {"alg":"HS256","typ":"JWT"}
|
||||
Payload: {"iss":"supabase","ref":"hgejqhubxceqppqkgqgx","role":"anon","sub":"uuid","aud":"authenticated"}
|
||||
→ Works for: api.apodex.ai/v1/api-keys (but Supabase project is dead)
|
||||
|
||||
API Key (sk-...):
|
||||
opaque base64-encoded string
|
||||
→ Works for: api.apodex.ai/v1/chat/completions, api.apodex.ai/v1/models
|
||||
```
|
||||
|
||||
## Rate Limit
|
||||
|
||||
- Endpoint: `POST /api/auth/passwordless/send-code`
|
||||
- After ~8-10 requests: HTTP 429
|
||||
- Body: `{"code":"PASSWORDLESS_SEND_RATE_LIMITED","message":"Operation too frequent, please try again later"}`
|
||||
- Scope: IP-based (not cookie-based)
|
||||
- Recovery: Wait 5-10 minutes
|
||||
- Bypass: Rotate IP via VPN/proxy
|
||||
|
||||
## Headers Observed in Response
|
||||
|
||||
```
|
||||
set-cookie: session=<uuid>; HttpOnly; Max-Age=3600; Path=/; SameSite=Lax
|
||||
```
|
||||
|
||||
This session cookie is set on send-code and may be needed for verify-login.
|
||||
Reference in New Issue
Block a user