157 lines
16 KiB
Plaintext
157 lines
16 KiB
Plaintext
page Authandpermissionsdashboarddemo {
|
|
seo {
|
|
title = "Auth and permissions dashboard demo"
|
|
description = "Create a complete demonstration site with public pages, authentication, configuration profiles, a protected dashboard, roles, permissions, database migrations, tests, and a production build."
|
|
canonical = "https://wrnexusjs.dev/guides/full-stack-auth-demo"
|
|
}
|
|
view {
|
|
<a href="#main" class="skip-link">Skip to content</a>
|
|
<div class="docs-shell">
|
|
<header class="topbar"><a class="brand" href="/"><span>W</span> WRNexusJS</a><nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav><div class="topbar-actions"><a class="preview-pill" href="/access">Preview · v0.8.8</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div></header>
|
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
|
<main id="main" class="portal-main docs-layout docs-layout--navigation"><aside class="docs-section-menu" aria-label="Documentation sections">
|
|
<nav>
|
|
<section><strong>Get started</strong><a href="/getting-started">Quick start</a><a href="/tutorial">Tutorial</a><a href="/guides/full-stack-auth-demo">Auth dashboard demo</a><a href="/guides/project-structure">Project structure</a><a href="/guides/configuration-and-profiles">Configuration</a></section>
|
|
<section><strong>Build</strong><a href="/guides/pages-and-components">Pages and components</a><a href="/guides/routing">Routing</a><a href="/guides/server-data">Server data</a><a href="/guides/forms-and-validation">Forms and validation</a><a href="/guides/database">Database</a></section>
|
|
<section><strong>Auth and security</strong><a href="/guides/authentication">Authentication</a><a href="/guides/authorization">Authorization</a><a href="/guides/security">Application security</a><a href="/security">Security policy</a></section>
|
|
<section><strong>Reference</strong><a href="/packages">Packages overview</a><a href="/packages/cli">CLI</a><a href="/language">.wrn language</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/examples">Examples</a></section>
|
|
<section class="package-menu"><strong>Packages</strong><a href="/packages/ai">@wrnexus/ai</a><a href="/packages/auth">@wrnexus/auth</a><a href="/packages/authz">@wrnexus/authz</a><a href="/packages/benchmark">@wrnexus/benchmark</a><a href="/packages/cache">@wrnexus/cache</a><a href="/packages/captcha">@wrnexus/captcha</a><a href="/packages/cli">@wrnexus/cli</a><a href="/packages/compiler">@wrnexus/compiler</a><a href="/packages/content">@wrnexus/content</a><a href="/packages/core">@wrnexus/core</a><a href="/packages/csr">@wrnexus/csr</a><a href="/packages/db">@wrnexus/db</a><a href="/packages/dev-server">@wrnexus/dev-server</a><a href="/packages/dev-toolbar">@wrnexus/dev-toolbar</a><a href="/packages/encryption">@wrnexus/encryption</a><a href="/packages/graphql">@wrnexus/graphql</a><a href="/packages/helpers">@wrnexus/helpers</a><a href="/packages/i18n">@wrnexus/i18n</a><a href="/packages/identity">@wrnexus/identity</a><a href="/packages/image">@wrnexus/image</a><a href="/packages/jwt">@wrnexus/jwt</a><a href="/packages/language-server">@wrnexus/language-server</a><a href="/packages/mcp">@wrnexus/mcp</a><a href="/packages/mobile">@wrnexus/mobile</a><a href="/packages/native">@wrnexus/native</a><a href="/packages/oauth">@wrnexus/oauth</a><a href="/packages/observability">@wrnexus/observability</a><a href="/packages/playground">@wrnexus/playground</a><a href="/packages/plugin">@wrnexus/plugin</a><a href="/packages/pubsub">@wrnexus/pubsub</a><a href="/packages/pwa">@wrnexus/pwa</a><a href="/packages/queue">@wrnexus/queue</a><a href="/packages/reactive">@wrnexus/reactive</a><a href="/packages/realtime">@wrnexus/realtime</a><a href="/packages/router">@wrnexus/router</a><a href="/packages/rpc">@wrnexus/rpc</a><a href="/packages/security">@wrnexus/security</a><a href="/packages/ssr">@wrnexus/ssr</a><a href="/packages/store">@wrnexus/store</a><a href="/packages/styles">@wrnexus/styles</a><a href="/packages/syntax">@wrnexus/syntax</a><a href="/packages/test">@wrnexus/test</a><a href="/packages/tracking">@wrnexus/tracking</a><a href="/packages/typecheck">@wrnexus/typecheck</a><a href="/packages/ui">@wrnexus/ui</a><a href="/packages/uploader">@wrnexus/uploader</a><a href="/packages/validation">@wrnexus/validation</a></section>
|
|
</nav>
|
|
</aside><div class="docs-reading-column"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span aria-hidden="true">/</span><span>Guides</span><span aria-hidden="true">/</span><span aria-current="page">Auth and permissions dashboard demo</span></nav><article class="documentation prose standalone"><span class="status status-beta">Preview guide · 0.8.8</span><h1>Auth and permissions dashboard demo</h1><p>This walkthrough builds a small team portal. Public visitors can read the home and pricing pages, users can sign in, and the dashboard separates ordinary members from administrators. Every authorization decision remains on the server.</p>
|
|
<h2 id="1-create-the-application">1. Create the application</h2><pre><code>bunx @wrnexus/cli@0.8.8 create team-portal
|
|
cd team-portal
|
|
bun install
|
|
bun add @wrnexus/auth @wrnexus/authz @wrnexus/db @wrnexus/validation @wrnexus/ui
|
|
bunx wrnexus generate page pricing
|
|
bunx wrnexus generate page login
|
|
bunx wrnexus generate page dashboard
|
|
bunx wrnexus generate api session/login
|
|
bunx wrnexus generate api session/logout
|
|
bunx wrnexus generate schema login
|
|
bunx wrnexus authz init --dialect=sqlite
|
|
bunx wrnexus db new initial_auth --from-models
|
|
bunx wrnexus db migrate
|
|
bunx wrnexus generate types .</code></pre><p>Expected success output includes created file paths, an initialized authorization catalog, the applied migration name, and the generated application declaration path.</p>
|
|
<h2 id="2-configure-profiles-and-security">2. Configure profiles and security</h2><pre><code>// wrnexus.config.ts
|
|
import type { AppConfig } from "@wrnexus/styles";
|
|
|
|
const config: AppConfig = {
|
|
seo: { title: "Team Portal", titleTemplate: "%s | Team Portal" },
|
|
theme: { default: "system", palette: "violet" },
|
|
security: {
|
|
contentSecurityPolicy: true,
|
|
csrf: true,
|
|
frameOptions: "deny",
|
|
},
|
|
profiles: {
|
|
development: { envFiles: [".env", ".env.development"] },
|
|
production: { envFiles: [".env", ".env.production"] },
|
|
},
|
|
};
|
|
|
|
export default config;</code></pre><pre><code># .env.example — commit names, never real secrets
|
|
DATABASE_URL=sqlite:./data/team-portal.db
|
|
SESSION_SECRET=replace-with-at-least-32-random-bytes
|
|
APP_ORIGIN=http://localhost:3000</code></pre><p>Run <code>wrnexus config . --explain --profile=production</code> before deployment and confirm no development fallback or secret value is printed.</p>
|
|
<h2 id="3-create-public-pages-and-layout">3. Create public pages and layout</h2><pre><code>// app/layouts/public.wrn
|
|
layout Public {
|
|
view {
|
|
<Navbar brand="Team Portal" />
|
|
<main><slot /></main>
|
|
<Footer copyright="Team Portal" />
|
|
}
|
|
}
|
|
|
|
// app/pages/index.wrn
|
|
page Home {
|
|
layout = "public"
|
|
seo { title = "Home" description = "A secure portal for modern teams." }
|
|
view {
|
|
<Hero eyebrow="Team operations" title="One secure place for every team." />
|
|
<FeatureGrid columns="3"><slot /></FeatureGrid>
|
|
}
|
|
}</code></pre><p>Create <code>pricing.wrn</code>, <code>privacy.wrn</code>, and <code>terms.wrn</code> with the same public layout. Public routes must not load private account data.</p>
|
|
<h2 id="4-define-login-validation-and-handlers">4. Define login validation and handlers</h2><pre><code>// app/schemas/login.ts
|
|
import { v } from "@wrnexus/validation";
|
|
export default v.object({
|
|
email: v.string().trim().email(),
|
|
password: v.string().min(12).max(128),
|
|
});
|
|
|
|
// app/api/session/login.ts
|
|
import schema from "../../schemas/login";
|
|
import { parseBody } from "@wrnexus/validation";
|
|
|
|
export const POST = async (ctx) => {
|
|
const parsed = await parseBody(schema, ctx.req);
|
|
if (!parsed.ok) return parsed.response;
|
|
// Look up the account, verify its password, rotate the session,
|
|
// and return the same failure shape for unknown users and bad passwords.
|
|
return Response.json({ ok: true, redirect: "/dashboard" });
|
|
};</code></pre><p>Use the exact installed <a href="/packages/auth">authentication package</a> API for account lookup, password verification, session rotation, rate limiting, and audit events. Do not copy placeholder authentication logic into production.</p>
|
|
<h2 id="5-declare-roles-and-permissions">5. Declare roles and permissions</h2><pre><code>// app/authz/main.ts
|
|
export const permissions = [
|
|
"dashboard:read",
|
|
"member:read",
|
|
"member:invite",
|
|
"member:manage",
|
|
] as const;
|
|
|
|
export const roles = {
|
|
member: ["dashboard:read", "member:read"],
|
|
manager: ["dashboard:read", "member:read", "member:invite"],
|
|
admin: ["dashboard:read", "member:read", "member:invite", "member:manage"],
|
|
} as const;</code></pre><pre><code>bunx wrnexus authz generate
|
|
bunx wrnexus authz list
|
|
bunx wrnexus contracts snapshot .</code></pre><p>Expected output lists the four permission identifiers and generated authorization artifacts. Commit the contract snapshot so later permission drift is reviewable.</p>
|
|
<h2 id="6-protect-the-dashboard-on-the-server">6. Protect the dashboard on the server</h2><pre><code>// app/middleware/auth.ts
|
|
export default async function requireUser(ctx, next) {
|
|
const user = await readAuthenticatedUser(ctx);
|
|
if (!user) return Response.redirect(new URL("/login", ctx.url), 303);
|
|
ctx.state.user = user;
|
|
return next();
|
|
}
|
|
|
|
// app/api/members.ts
|
|
export const GET = async (ctx) => {
|
|
await requirePermission(ctx, "member:read");
|
|
return Response.json({ members: await listMembers(ctx.state.user.tenantId) });
|
|
};
|
|
|
|
export const POST = async (ctx) => {
|
|
await requirePermission(ctx, "member:invite");
|
|
// Validate input and keep the tenant identifier server-owned.
|
|
return Response.json({ ok: true }, { status: 201 });
|
|
};</code></pre><p>Route middleware establishes identity; each API mutation still checks its exact permission and resource boundary. Hiding an Invite button is useful UX but never authorization.</p>
|
|
<h2 id="7-render-the-dashboard">7. Render the dashboard</h2><pre><code>page Dashboard {
|
|
layout = "dashboard"
|
|
ssr {
|
|
api summary GET /api/dashboard { return summary }
|
|
api members GET /api/members { return members }
|
|
}
|
|
view {
|
|
<PageHeader eyebrow="Workspace" title="Dashboard" />
|
|
<MetricGrid columns="3">
|
|
<MetricCard label="Members" value={summary.memberCount} />
|
|
<MetricCard label="Invitations" value={summary.invitationCount} />
|
|
<MetricCard label="Active today" value={summary.activeToday} />
|
|
</MetricGrid>
|
|
<DataTable rows={members} />
|
|
}
|
|
}</code></pre><p>Keep dashboard data tenant-scoped in the API. Server rendering prevents an empty shell, while the client receives only the modules required for interactive controls.</p>
|
|
<h2 id="8-test-denial-paths-and-production">8. Test denial paths and production</h2><pre><code>bunx wrnexus typecheck .
|
|
bunx wrnexus test unit .
|
|
bunx wrnexus test api .
|
|
bunx wrnexus test browser .
|
|
bunx wrnexus security audit .
|
|
bunx wrnexus contracts check .
|
|
bunx wrnexus build .
|
|
bunx wrnexus preview . --port=3000</code></pre><p>Tests should prove anonymous dashboard access redirects, members cannot invite, managers can invite but cannot manage roles, administrators can manage roles, cross-tenant identifiers are rejected, login failures are rate-limited, CSRF failures return 403, and the production server starts from <code>dist/server.js</code>.</p>
|
|
<h2 id="9-demo-checklist">9. Demo checklist</h2><ul><li>Public home, pricing, privacy, and terms pages render without authentication.</li><li>Login creates and rotates a secure session.</li><li>Dashboard navigation changes by permission, while APIs enforce every permission independently.</li><li>Member lists and mutations are tenant-scoped on the server.</li><li>Development and production profiles resolve explicitly.</li><li>Typecheck, API tests, browser tests, security audit, contract check, build, and preview all pass.</li></ul><h2 id="configuration">Configuration</h2><p>Keep configuration in <code>wrnexus.config.ts</code>, select an explicit profile, and store secrets only in validated environment variables. Use <code>wrnexus config . --explain</code> to review the resolved non-secret configuration.</p><h2 id="implementation-workflow">Implementation workflow</h2><pre><code>bunx wrnexus doctor .
|
|
bunx wrnexus typecheck .
|
|
bunx wrnexus inspect routes .
|
|
bunx wrnexus build .</code></pre><p>Start from the exact installed package page, implement the smallest server-owned contract, and add browser behavior only where interaction requires it. Run the production build because development-only success does not prove deployability.</p><h2 id="verification-checklist">Verification checklist</h2><ul><li>Inputs are validated at the authoritative server boundary.</li><li>Authentication and resource authorization are tested independently.</li><li>Generated routes and application types are current.</li><li>Error, empty, loading, denied, and success states are documented.</li><li>The production artifact starts and serves the expected route.</li></ul><h2 id="release-scope">Release scope</h2><p>This guide describes installed 0.8.8 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.</p><p><a href="/packages">Browse package APIs</a> · <a href="/packages/cli">CLI reference</a> · <a href="/guides/troubleshooting">Troubleshooting</a> · <a href="/support">Support</a></p></article></div><aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#1-create-the-application">1. Create the application</a><a class="toc-level-2" href="#2-configure-profiles-and-security">2. Configure profiles and security</a><a class="toc-level-2" href="#3-create-public-pages-and-layout">3. Create public pages and layout</a><a class="toc-level-2" href="#4-define-login-validation-and-handlers">4. Define login validation and handlers</a><a class="toc-level-2" href="#5-declare-roles-and-permissions">5. Declare roles and permissions</a><a class="toc-level-2" href="#6-protect-the-dashboard-on-the-server">6. Protect the dashboard on the server</a><a class="toc-level-2" href="#7-render-the-dashboard">7. Render the dashboard</a><a class="toc-level-2" href="#8-test-denial-paths-and-production">8. Test denial paths and production</a><a class="toc-level-2" href="#9-demo-checklist">9. Demo checklist</a><a class="toc-level-2" href="#configuration">Configuration</a><a class="toc-level-2" href="#implementation-workflow">Implementation workflow</a><a class="toc-level-2" href="#verification-checklist">Verification checklist</a><a class="toc-level-2" href="#release-scope">Release scope</a></nav></aside></main>
|
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.8.8</strong><span>Server-first documentation for the Bun-native framework.</span></p></div><nav aria-label="Footer"><a href="/access">Request access</a><a href="/license">License</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Created by <a href="https://workroot.in/">WorkRoot</a> · Private Developer Preview</p></footer>
|
|
</div>
|
|
}
|
|
} |