feat : able to use mutil email
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -103,7 +103,7 @@ export function RegisterCard() {
|
||||
<CircleAlertIcon aria-hidden="true" />
|
||||
<AlertTitle>หน้าลงทะเบียนชั่วคราว</AlertTitle>
|
||||
<AlertDescription>
|
||||
ระบบรับเฉพาะอีเมลที่ตรงกับ ADMIN_EMAIL เท่านั้น
|
||||
ระบบรับเฉพาะอีเมลที่ระบุไว้ใน ADMIN_EMAIL เท่านั้น
|
||||
ควรนำหน้านี้ออกหลังสร้างบัญชีสำเร็จ
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -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,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(
|
||||
|
||||
Reference in New Issue
Block a user