mirror of
https://github.com/wahyd4/heygo.git
synced 2026-08-08 21:05:48 +10:00
update dev env
This commit is contained in:
@@ -123,6 +123,21 @@ The Worker reads `PUBLIC_HOST`, `PRIVATE_HOST`, `APP_BASE_URL`, and
|
|||||||
`COOKIE_DOMAIN` from env vars (set in each wrangler config), so the same code
|
`COOKIE_DOMAIN` from env vars (set in each wrangler config), so the same code
|
||||||
serves every environment without hardcoded hostnames.
|
serves every environment without hardcoded hostnames.
|
||||||
|
|
||||||
|
Admin users are configured with `ADMIN_EMAILS` in the matching Wrangler config:
|
||||||
|
|
||||||
|
- `wrangler.dev.jsonc` for `dev.heygo.cc`
|
||||||
|
- `wrangler.prod.jsonc` for `heygo.cc`
|
||||||
|
- `wrangler.jsonc` for local development
|
||||||
|
|
||||||
|
Use a comma, semicolon, space, or newline separated list, for example:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
"ADMIN_EMAILS": "owner@example.com, ops@example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
When a signed-in user's email matches this list, the app treats that user as
|
||||||
|
`admin` even if the stored database role is still `user`.
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- A Cloudflare account with the `heygo.cc` zone added.
|
- A Cloudflare account with the `heygo.cc` zone added.
|
||||||
|
|||||||
+66
-1
@@ -3,7 +3,9 @@ import {
|
|||||||
AUTH_SESSION_COOKIE_NAME,
|
AUTH_SESSION_COOKIE_NAME,
|
||||||
AuthError,
|
AuthError,
|
||||||
getCurrentUser,
|
getCurrentUser,
|
||||||
|
getConfiguredAdminEmails,
|
||||||
hashSessionToken,
|
hashSessionToken,
|
||||||
|
isConfiguredAdminEmail,
|
||||||
parseCookieHeader,
|
parseCookieHeader,
|
||||||
requireAdmin,
|
requireAdmin,
|
||||||
requireUser,
|
requireUser,
|
||||||
@@ -64,7 +66,7 @@ class FakeSessionStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeEnv(rows: SessionRow[] = []): { env: Env; db: FakeSessionD1 } {
|
function makeEnv(rows: SessionRow[] = [], overrides: Partial<Env> = {}): { env: Env; db: FakeSessionD1 } {
|
||||||
const db = new FakeSessionD1(rows);
|
const db = new FakeSessionD1(rows);
|
||||||
return {
|
return {
|
||||||
env: {
|
env: {
|
||||||
@@ -73,6 +75,7 @@ function makeEnv(rows: SessionRow[] = []): { env: Env; db: FakeSessionD1 } {
|
|||||||
PRIVATE_HOST: 'my.heygo.cc',
|
PRIVATE_HOST: 'my.heygo.cc',
|
||||||
APP_BASE_URL: 'https://heygo.cc',
|
APP_BASE_URL: 'https://heygo.cc',
|
||||||
COOKIE_DOMAIN: '.heygo.cc',
|
COOKIE_DOMAIN: '.heygo.cc',
|
||||||
|
...overrides,
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
@@ -129,6 +132,19 @@ describe('hashSessionToken', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('configured admin emails', () => {
|
||||||
|
it('parses comma, semicolon, and whitespace separated admin emails case-insensitively', () => {
|
||||||
|
const emails = getConfiguredAdminEmails({
|
||||||
|
ADMIN_EMAILS: ' Boss@Heygo.cc, owner@example.com; ops@example.com\n',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect([...emails]).toEqual(['boss@heygo.cc', 'owner@example.com', 'ops@example.com']);
|
||||||
|
expect(isConfiguredAdminEmail('boss@heygo.cc', { ADMIN_EMAILS: 'Boss@Heygo.cc' })).toBe(true);
|
||||||
|
expect(isConfiguredAdminEmail('BOSS@HEYGO.CC', { ADMIN_EMAILS: 'boss@heygo.cc' })).toBe(true);
|
||||||
|
expect(isConfiguredAdminEmail('person@heygo.cc', { ADMIN_EMAILS: 'boss@heygo.cc' })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getCurrentUser', () => {
|
describe('getCurrentUser', () => {
|
||||||
it('returns null when there is no Cookie header', async () => {
|
it('returns null when there is no Cookie header', async () => {
|
||||||
const { env } = makeEnv();
|
const { env } = makeEnv();
|
||||||
@@ -176,6 +192,30 @@ describe('getCurrentUser', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('treats a configured admin email as admin even when the database role is user', async () => {
|
||||||
|
const token = 'configured-admin-token';
|
||||||
|
const hash = await hashSessionToken(token);
|
||||||
|
const { env } = makeEnv(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'user_configured_admin',
|
||||||
|
email: 'Boss@Heygo.cc',
|
||||||
|
name: 'Boss',
|
||||||
|
image_url: null,
|
||||||
|
role: 'user',
|
||||||
|
expires_at: futureIso(),
|
||||||
|
session_token_hash: hash,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ ADMIN_EMAILS: 'boss@heygo.cc' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const cookie = `${AUTH_SESSION_COOKIE_NAME}=${token}`;
|
||||||
|
const user = await getCurrentUser(makeRequest(cookie), env);
|
||||||
|
|
||||||
|
expect(user?.role).toBe('admin');
|
||||||
|
});
|
||||||
|
|
||||||
it('returns null for an expired session', async () => {
|
it('returns null for an expired session', async () => {
|
||||||
const token = 'expired-session-token';
|
const token = 'expired-session-token';
|
||||||
const hash = await hashSessionToken(token);
|
const hash = await hashSessionToken(token);
|
||||||
@@ -272,4 +312,29 @@ describe('requireAdmin', () => {
|
|||||||
expect(user.id).toBe('user_5');
|
expect(user.id).toBe('user_5');
|
||||||
expect(user.role).toBe('admin');
|
expect(user.role).toBe('admin');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows a configured admin email through requireAdmin', async () => {
|
||||||
|
const token = 'configured-admin-require-token';
|
||||||
|
const hash = await hashSessionToken(token);
|
||||||
|
const { env } = makeEnv(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'user_6',
|
||||||
|
email: 'owner@heygo.cc',
|
||||||
|
name: 'Owner',
|
||||||
|
image_url: null,
|
||||||
|
role: 'user',
|
||||||
|
expires_at: futureIso(),
|
||||||
|
session_token_hash: hash,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ ADMIN_EMAILS: 'owner@heygo.cc' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const cookie = `${AUTH_SESSION_COOKIE_NAME}=${token}`;
|
||||||
|
const user = await requireAdmin(makeRequest(cookie), env);
|
||||||
|
|
||||||
|
expect(user.id).toBe('user_6');
|
||||||
|
expect(user.role).toBe('admin');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+27
-1
@@ -24,6 +24,7 @@ export interface AuthSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ENABLED_PROVIDER_SET = new Set<string>(AUTH_ENABLED_PROVIDERS);
|
const ENABLED_PROVIDER_SET = new Set<string>(AUTH_ENABLED_PROVIDERS);
|
||||||
|
const ADMIN_EMAIL_SPLIT_PATTERN = /[\s,;]+/;
|
||||||
|
|
||||||
export function isAuthProviderEnabled(provider: string): provider is EnabledAuthProvider {
|
export function isAuthProviderEnabled(provider: string): provider is EnabledAuthProvider {
|
||||||
return ENABLED_PROVIDER_SET.has(provider);
|
return ENABLED_PROVIDER_SET.has(provider);
|
||||||
@@ -96,6 +97,31 @@ export async function hashSessionToken(token: string): Promise<string> {
|
|||||||
return bufferToHex(digest);
|
return bufferToHex(digest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getConfiguredAdminEmails(env: Pick<Env, 'ADMIN_EMAILS'>): Set<string> {
|
||||||
|
const raw = env.ADMIN_EMAILS?.trim();
|
||||||
|
if (!raw) {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Set(
|
||||||
|
raw
|
||||||
|
.split(ADMIN_EMAIL_SPLIT_PATTERN)
|
||||||
|
.map((email) => email.trim().toLowerCase())
|
||||||
|
.filter(Boolean),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isConfiguredAdminEmail(
|
||||||
|
email: string | null | undefined,
|
||||||
|
env: Pick<Env, 'ADMIN_EMAILS'>,
|
||||||
|
): boolean {
|
||||||
|
if (!email) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getConfiguredAdminEmails(env).has(email.trim().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
function bufferToHex(buffer: ArrayBuffer): string {
|
function bufferToHex(buffer: ArrayBuffer): string {
|
||||||
const bytes = new Uint8Array(buffer);
|
const bytes = new Uint8Array(buffer);
|
||||||
let hex = '';
|
let hex = '';
|
||||||
@@ -148,7 +174,7 @@ export async function getCurrentUser(request: Request, env: Env): Promise<AuthUs
|
|||||||
email: row.email,
|
email: row.email,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
imageUrl: row.image_url,
|
imageUrl: row.image_url,
|
||||||
role: row.role,
|
role: isConfiguredAdminEmail(row.email, env) ? 'admin' : row.role,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ export interface Env {
|
|||||||
PRIVATE_HOST: string;
|
PRIVATE_HOST: string;
|
||||||
APP_BASE_URL: string;
|
APP_BASE_URL: string;
|
||||||
COOKIE_DOMAIN: string;
|
COOKIE_DOMAIN: string;
|
||||||
|
ADMIN_EMAILS?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -23,7 +23,8 @@
|
|||||||
"PUBLIC_HOST": "dev.heygo.cc",
|
"PUBLIC_HOST": "dev.heygo.cc",
|
||||||
"PRIVATE_HOST": "my.dev.heygo.cc",
|
"PRIVATE_HOST": "my.dev.heygo.cc",
|
||||||
"APP_BASE_URL": "https://dev.heygo.cc",
|
"APP_BASE_URL": "https://dev.heygo.cc",
|
||||||
"COOKIE_DOMAIN": ".heygo.cc"
|
"COOKIE_DOMAIN": ".heygo.cc",
|
||||||
|
"ADMIN_EMAILS": "wahyd4@gmail.com"
|
||||||
},
|
},
|
||||||
"d1_databases": [
|
"d1_databases": [
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,8 @@
|
|||||||
"PUBLIC_HOST": "*",
|
"PUBLIC_HOST": "*",
|
||||||
"PRIVATE_HOST": "*",
|
"PRIVATE_HOST": "*",
|
||||||
"APP_BASE_URL": "http://localhost:8787",
|
"APP_BASE_URL": "http://localhost:8787",
|
||||||
"COOKIE_DOMAIN": ""
|
"COOKIE_DOMAIN": "",
|
||||||
|
"ADMIN_EMAILS": "wahyd4@gmail.com"
|
||||||
},
|
},
|
||||||
"d1_databases": [
|
"d1_databases": [
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,8 @@
|
|||||||
"PUBLIC_HOST": "heygo.cc",
|
"PUBLIC_HOST": "heygo.cc",
|
||||||
"PRIVATE_HOST": "my.heygo.cc",
|
"PRIVATE_HOST": "my.heygo.cc",
|
||||||
"APP_BASE_URL": "https://heygo.cc",
|
"APP_BASE_URL": "https://heygo.cc",
|
||||||
"COOKIE_DOMAIN": ".heygo.cc"
|
"COOKIE_DOMAIN": ".heygo.cc",
|
||||||
|
"ADMIN_EMAILS": "wahyd4@gmail.com"
|
||||||
},
|
},
|
||||||
"d1_databases": [
|
"d1_databases": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user