Skip to content

Commit 10224dc

Browse files
authored
[sdk-playground] Verify Proxy Auth Headers (#82)
* [sdk-playground/server] split utils into lib * [sdk-playground/server] withProxyAuth to lib/crypto * [sdk-playground/client] Proxy Auth Example page * add pages functions middleware with PROXY_AUTH_MODE * use shared package * add PROXY_AUTH_MODE to example.env * add allowedHosts:true to vite config * shared package gitignore * public key is hex encoded * fix bottom page of scroll being broken * remove dist oops * remove shared/package-lock.json * put the html into a templates file * pnpm run fix * fix nullish hexBytes issue * build shared in github workflows
1 parent 989c6d3 commit 10224dc

27 files changed

+522
-19
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ jobs:
2121
node-version: 20
2222
cache: pnpm
2323
- run: pnpm install
24+
- run: pnpm --filter "./sdk-playground/packages/shared" build
25+
- run: pnpm --filter "./sdk-playground/packages/server" build
26+
- run: pnpm --filter "./sdk-playground/packages/client" build
2427
- run: pnpm lint

.github/workflows/sdk-playground-production.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ jobs:
2121
run: |
2222
cd sdk-playground
2323
echo "${{ secrets.ENV_FILE }}" > .env.production
24+
- name: build sdk-playground shared
25+
run: |
26+
cd sdk-playground/packages/shared
27+
pnpm build
2428
- name: build sdk-playground client
2529
run: |
2630
cd sdk-playground/packages/client

.github/workflows/sdk-playground.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
node-version: 20
2424
cache: pnpm
2525
- run: pnpm install
26+
- run: npm run build
27+
working-directory: sdk-playground/packages/shared
2628
- run: npm run build
2729
working-directory: sdk-playground/packages/server
2830
- run: npm run build

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk-playground/example.env

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ VITE_CLIENT_ID=123456789
77
VITE_APPLICATION_ID=123456789
88
VITE_DISCORD_API_BASE=https://discord.com/api
99

10-
CLIENT_SECRET=secret # This should be the oauth2 token for your application from the developer portal.
1110
BOT_TOKEN=bottoken # This should be the bot token for your application from the developer portal.
12-
WEBAPP_SERVE_PORT=3000
11+
CLIENT_SECRET=secret # This should be the oauth2 token for your application from the developer portal.
12+
PROXY_AUTH_MODE='log-only'
13+
PUBLIC_KEY=publickey # This should be the publickey for your application from the developer portal.
14+
WEBAPP_SERVE_PORT=3000
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { verifyProxyAuth } from 'shared';
2+
import type { Env } from 'shared';
3+
import { AUTH_REQUIRED_HTML, SERVER_ERROR_HTML } from './templates';
4+
5+
type AuthMode = 'enforce' | 'log-only' | 'disabled';
6+
7+
function getAuthMode(request: Request, env: Env): AuthMode {
8+
const url = new URL(request.url);
9+
10+
// Check query parameter first (for easy testing)
11+
const queryMode = url.searchParams.get('proxy_auth');
12+
if (
13+
queryMode === 'enforce' ||
14+
queryMode === 'log-only' ||
15+
queryMode === 'disabled'
16+
) {
17+
return queryMode;
18+
}
19+
20+
if (env.PROXY_AUTH_MODE) {
21+
return env.PROXY_AUTH_MODE;
22+
}
23+
24+
return 'log-only';
25+
}
26+
27+
function shouldSkipAuth(request: Request, env: Env): boolean {
28+
const url = new URL(request.url);
29+
30+
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
31+
return true;
32+
}
33+
34+
return false;
35+
}
36+
37+
export async function onRequest(context: {
38+
request: Request;
39+
env: Env;
40+
next: () => Promise<Response>;
41+
}): Promise<Response> {
42+
const { request, env, next } = context;
43+
const url = new URL(request.url);
44+
45+
try {
46+
const authMode = getAuthMode(request, env);
47+
console.log(`[Proxy Auth] Mode: ${authMode}, URL: ${url.pathname}`);
48+
49+
if (shouldSkipAuth(request, env)) {
50+
console.log('[Proxy Auth] Bypassed for development/testing');
51+
return await next();
52+
}
53+
54+
if (authMode === 'disabled') {
55+
console.log('[Proxy Auth] Authentication disabled, allowing request');
56+
return await next();
57+
}
58+
59+
const proxyToken = await verifyProxyAuth(request, env);
60+
61+
if (!proxyToken) {
62+
console.log('[Proxy Auth] Validation failed - no valid token');
63+
64+
// In log-only mode, allow the request but log the failure
65+
if (authMode === 'log-only') {
66+
console.log(
67+
'[Proxy Auth] Log-only mode: allowing request despite auth failure',
68+
);
69+
return await next();
70+
}
71+
72+
console.log('[Proxy Auth] Enforce mode: blocking request');
73+
return new Response(AUTH_REQUIRED_HTML, {
74+
status: 401,
75+
headers: {
76+
'Content-Type': 'text/html',
77+
'Cache-Control': 'no-cache, no-store, must-revalidate',
78+
},
79+
});
80+
}
81+
82+
console.log(
83+
`[Proxy Auth] Validation success for user ${proxyToken.user_id} (app: ${proxyToken.application_id})`,
84+
);
85+
86+
return await next();
87+
} catch (error) {
88+
console.error('[Proxy Auth] Middleware error:', error);
89+
90+
// In log-only mode, allow the request despite errors
91+
const authMode = getAuthMode(request, env);
92+
if (authMode === 'log-only') {
93+
console.log('[Proxy Auth] Log-only mode: allowing request despite error');
94+
return await next();
95+
}
96+
return new Response(SERVER_ERROR_HTML, {
97+
status: 500,
98+
headers: {
99+
'Content-Type': 'text/html',
100+
'Cache-Control': 'no-cache, no-store, must-revalidate',
101+
},
102+
});
103+
}
104+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// HTML templates for error pages
2+
export const AUTH_REQUIRED_HTML = `<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<title>Discord Authentication Required</title>
6+
<style>
7+
body {
8+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
9+
max-width: 600px;
10+
margin: 100px auto;
11+
padding: 20px;
12+
text-align: center;
13+
}
14+
.error { color: #f04747; }
15+
.info { color: #5865f2; margin-top: 20px; }
16+
</style>
17+
</head>
18+
<body>
19+
<h1 class="error">🔒 Discord Authentication Required</h1>
20+
<p>This application requires Discord proxy authentication.</p>
21+
<p>Please access this app through Discord's embedded app system.</p>
22+
<p class="info">Add <code>?proxy_auth=log-only</code> to bypass auth for testing.</p>
23+
</body>
24+
</html>`;
25+
26+
export const SERVER_ERROR_HTML = `<!DOCTYPE html>
27+
<html>
28+
<head>
29+
<title>Server Error</title>
30+
<style>
31+
body {
32+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
33+
max-width: 600px;
34+
margin: 100px auto;
35+
padding: 20px;
36+
text-align: center;
37+
}
38+
.error { color: #f04747; }
39+
</style>
40+
</head>
41+
<body>
42+
<h1 class="error">⚠️ Server Error</h1>
43+
<p>An error occurred while validating authentication.</p>
44+
<p>Add <code>?proxy_auth=disabled</code> to skip auth for debugging.</p>
45+
</body>
46+
</html>`;

sdk-playground/packages/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"react-router": "^6.16.0",
2626
"react-router-dom": "^6.16.0",
2727
"react-use": "^17.2.4",
28+
"shared": "workspace:*",
2829
"web-vitals": "^3.0.0",
2930
"zustand": "^4.1.4"
3031
},

sdk-playground/packages/client/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {useState} from 'react';
4646
import GetActivityInstance from "./pages/GetActivityInstance";
4747
import CloseActivity from "./pages/CloseActivity";
4848
import Quests from "./pages/Quests";
49+
import ProxyAuthExample from "./pages/ProxyAuthExample";
4950

5051
import discordSdk from './discordSdk';
5152

@@ -253,6 +254,11 @@ const routes: Record<string, AppRoute> = {
253254
path: '/quests',
254255
name: 'Quests',
255256
component: Quests
257+
},
258+
proxyAuthExample: {
259+
path: '/proxy-auth-example',
260+
name: 'Proxy Auth Example',
261+
component: ProxyAuthExample
256262
}
257263
};
258264

sdk-playground/packages/client/src/AppStyles.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const Navigation = styled(ScrollArea.Root, {
1313
export const Ul = styled('ul', {
1414
display: 'flex',
1515
flexDirection: 'column',
16+
paddingBottom: '36px',
1617
});
1718

1819
export const Li = styled(Link, {

0 commit comments

Comments
 (0)