feat : able to use mutil email
CI / Verify (push) Successful in 54s
CI / Build immutable images and deploy (push) Has been skipped

This commit is contained in:
2026-08-29 08:18:21 +00:00 Unverified
parent 659a57fb4a
commit 6a932baa98
6 changed files with 29 additions and 6 deletions
+1
View File
@@ -9,6 +9,7 @@ DATABASE_POOL_SIZE=10
# Better Auth email/password administrator login
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=replace-with-at-least-32-random-bytes
# Separate multiple administrator addresses with commas.
ADMIN_EMAIL=[email protected]
# Redis remote cache, event transport, and outbox worker
+1 -1
View File
@@ -96,7 +96,7 @@ The admin uses a visual Notion-style block editor with forms, cards, drag-and-dr
- Cache validated public page snapshots by their page and data-source version vector. Use an external Redis-backed Next.js remote cache and tag handler so both application replicas share cache state and invalidations. Keep admin routes dynamic.
- Write cache invalidation and SSE work to a transactional outbox with the content mutation. A retrying worker invalidates affected page, directory, and data-source tags only after the database commit, then publishes typed Redis events.
- Provide Redis-backed, invalidation-only SSE streams for public pages, the character directory, and authenticated admin sessions. Events carry only opaque IDs and versions; clients refetch authoritative state on connection, reconnection, or notification. Send 90-second heartbeats, close streams after 30 minutes so clients reconnect, and disable Traefik response buffering. SSE is never a source of correctness and never carries page content.
- Authenticate with Better Auth email and password. Permit registration and admin access only when the credential account email exactly equals `ADMIN_EMAIL`; use a temporary `/register` bootstrap page and repeat authorization checks in every mutation, media, and administrative endpoint.
- Authenticate with Better Auth email and password. Permit registration and admin access only when the credential account email exactly matches an address in the comma-separated `ADMIN_EMAIL` configuration; use a temporary `/register` bootstrap page and repeat authorization checks in every mutation, media, and administrative endpoint.
- Upload PNG, JPEG, WebP, and GIF assets up to 20 MB through short-lived presigned requests. Serve them through same-origin `/media/[id]` responses. Allow public access only while an asset is referenced by a currently visible snapshot; otherwise require admin authentication. Retain objects while referenced by either current content or retained revisions.
- Applying a template clones independent pages, blocks, and data sources. Later template edits affect only future applications.
- Do not provide custom HTML, JavaScript, TypeScript, React, CSS, code blocks that execute, external scripts, arbitrary npm packages, or network-capable extensions.
+3 -2
View File
@@ -68,8 +68,9 @@ cp .env.example .env
`.env.example` contains placeholders only. Configure `.env` yourself; it is
ignored by Git and must never be committed. `BETTER_AUTH_URL` must exactly
match the application origin. Visit `/register` once to create the credential
account using the exact `ADMIN_EMAIL`, then use `/admin/login` for later access.
The registration endpoint rejects every other email; remove the temporary page
account using an address listed in `ADMIN_EMAIL`, then use `/admin/login` for
later access. Separate multiple administrator addresses with commas. The
registration endpoint rejects every other email; remove the temporary page
after the administrator account has been created.
Prepare the database and start the application:
+1 -1
View File
@@ -103,7 +103,7 @@ export function RegisterCard() {
<CircleAlertIcon aria-hidden="true" />
<AlertTitle>หน้าลงทะเบียนชั่วคราว</AlertTitle>
<AlertDescription>
ระบบรับเฉพาะอีเมลที่ตรงกับ ADMIN_EMAIL เท่านั้น
ระบบรับเฉพาะอีเมลที่ระบุไว้ใน ADMIN_EMAIL เท่านั้น
ควรนำหน้านี้ออกหลังสร้างบัญชีสำเร็จ
</AlertDescription>
</Alert>
+15
View File
@@ -23,6 +23,21 @@ describe("admin authorization", () => {
expect(isConfiguredAdminEmail(verified.email, undefined)).toBe(false);
});
it("supports a comma-separated administrator email list", () => {
expect(
isConfiguredAdminEmail(
"[email protected]",
"[email protected], [email protected]",
),
).toBe(true);
expect(
isConfiguredAdminEmail(
"[email protected]",
"[email protected], [email protected]",
),
).toBe(false);
});
it("allows only credential registration for the configured administrator", () => {
expect(
isAllowedAdminRegistration(
+8 -2
View File
@@ -8,9 +8,15 @@ export interface SessionUserLike {
export function isConfiguredAdminEmail(
email: string | null | undefined,
adminEmail: string | null | undefined,
adminEmailConfig: string | null | undefined,
): boolean {
return Boolean(email && adminEmail && email === adminEmail);
if (!email || !adminEmailConfig) return false;
return adminEmailConfig
.split(",")
.map((configuredEmail) => configuredEmail.trim())
.filter(Boolean)
.includes(email);
}
export function isAllowedAdminRegistration(