Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/template
REDIS_URL=redis://localhost:6379

# Auth
# AUTH_METHOD options: magic-link | pki | pki-and-magic-link | google-oauth | other
# AUTH_METHOD options: magic-link | pki | pki-and-magic-link | google-oauth | other | none
AUTH_METHOD=magic-link
BETTER_AUTH_SECRET=replace-with-32-byte-random-string
BETTER_AUTH_URL=http://localhost:3000
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.9
1.0.10
2 changes: 2 additions & 0 deletions apps/web/src/lib/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ const build = () => {
return { type: "google-oauth" as const };
case "other":
return { type: "other" as const };
case "none":
return { type: "none" as const };
default:
return { type: "magic-link" as const, sendMagicLink };
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const serverEnvSchema = z.object({
LANGFUSE_SECRET_KEY: z.string().optional(),
LANGFUSE_HOST: z.string().url().optional(),
AUTH_METHOD: z
.enum(["magic-link", "pki", "pki-and-magic-link", "google-oauth", "other"])
.enum(["magic-link", "pki", "pki-and-magic-link", "google-oauth", "other", "none"])
.default("magic-link"),
PKI_TRUSTED_PROXY_IPS: z.string().optional(),
PKI_SESSION_TTL_HOURS: z.coerce.number().int().positive().default(8),
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const PKI_MODES = new Set(["pki", "pki-and-magic-link"]);
* cert headers are consumed server-side; all other modes go to /admin/login.
*/
export const middleware = (req: NextRequest): NextResponse => {
if (AUTH_METHOD === "none") {
return NextResponse.next();
}

const { pathname } = req.nextUrl;

if (!pathname.startsWith("/admin") || pathname.startsWith("/admin/login")) {
Expand Down
54 changes: 54 additions & 0 deletions docs/development/implemented/v1.0.10/auth-none-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Auth Method: none

## Version Bump
PATCH — 1.0.9 → 1.0.10 (no DB schema changes)

## What & Why
Add `none` as a valid value for `AUTH_METHOD`. When selected, the middleware
skips all authentication checks and all `/admin/*` routes are publicly
accessible with no session required. Intended for local development, internal
tools, or deployments where the network perimeter provides access control.

## Affected Files

### `packages/adapters/src/auth/better-auth.ts`
- Extend `AuthMethod` union with `{ readonly type: "none" }`.
- `createAuth` treats `none` the same as `other`: no plugins, no error thrown.
Better Auth is still initialised so the `/api/auth/*` routes remain mounted.

### `apps/web/src/lib/env.ts`
- Add `"none"` to the `AUTH_METHOD` z.enum values.

### `apps/web/src/lib/container.ts`
- Add `case "none": return { type: "none" as const }` to the `authMethod`
factory switch.

### `apps/web/src/middleware.ts`
- When `AUTH_METHOD === "none"` return `NextResponse.next()` immediately,
skipping all session and redirect logic for `/admin/*` routes.

### `scripts/init-project.sh`
- Add option `6) none (no authentication — all routes public)` to the auth
method prompt.
- Map choice `6` → `AUTH_METHOD="none"`.
- When `AUTH_METHOD=none`, add a warning that all admin routes are unprotected.

### `packages/create/src/index.ts`
- Add `authMethod` prompt (select, after AI provider) to `collectInputs`.
- Add `authMethod` to `ScaffoldOptions` and the Summary display.
- Include `AUTH_METHOD` in `envReplacements` so `.env` is written correctly.
- Add `none` option to the choices list, labelled "None (all routes public)".

### `.env.example`
- Update the `AUTH_METHOD` comment line to include `none`:
`# AUTH_METHOD options: magic-link | pki | pki-and-magic-link | google-oauth | other | none`

## Acceptance Criteria
1. `AUTH_METHOD=none` passes Zod validation in `env.ts`.
2. `AuthMethod` type includes `{ type: "none" }`.
3. Middleware with `AUTH_METHOD=none` returns `NextResponse.next()` for all
`/admin/*` paths (no redirect, no session check).
4. `init-project.sh` offers option 6 and sets `AUTH_METHOD=none` in `.env.example`.
5. `packages/create` prompts for auth method including `none`, writes correct
`.env`.
6. `validate.sh` passes on 1.0.10.
25 changes: 25 additions & 0 deletions docs/development/implemented/v1.0.10/implementation-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Implementation Summary — v1.0.10 Auth Method: none

**Version bump:** 1.0.9 → 1.0.10 (PATCH — no DB schema changes)

## What was built

Added `none` as a valid `AUTH_METHOD` option. When selected, the middleware
short-circuits all authentication checks and all routes (including `/admin/*`)
are publicly accessible with no session required. Intended for local
development, internal tools, or deployments where network perimeter controls
access.

## Files modified

| Path | Change |
|---|---|
| `packages/adapters/src/auth/better-auth.ts` | Added `{ readonly type: "none" }` to `AuthMethod` union |
| `apps/web/src/lib/env.ts` | Added `"none"` to `AUTH_METHOD` Zod enum |
| `apps/web/src/lib/container.ts` | Added `case "none"` to authMethod factory switch |
| `apps/web/src/middleware.ts` | Early `NextResponse.next()` return when `AUTH_METHOD === "none"` |
| `scripts/init-project.sh` | Added option 6 for `none`; warning when selected |
| `packages/create/src/index.ts` | Added `authMethod` select prompt; wired through `ScaffoldOptions` and `envReplacements` |
| `.env.example` | Updated `AUTH_METHOD` comment to include `none` |
| `VERSION` | 1.0.9 → 1.0.10 |
| `package.json` | 1.0.9 → 1.0.10 |
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "template",
"version": "1.0.9",
"version": "1.0.10",
"private": true,
"description": "Production-ready AI application monorepo template (hexagonal architecture).",
"packageManager": "pnpm@9.12.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/adapters/src/auth/better-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export type AuthMethod =
readonly sendMagicLink: (params: { email: string; url: string }) => Promise<void>;
}
| { readonly type: "google-oauth" }
| { readonly type: "other" };
| { readonly type: "other" }
| { readonly type: "none" };

export interface AuthConfig {
readonly secret: string;
Expand Down
27 changes: 26 additions & 1 deletion packages/create/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ const FRAMEWORK_SCOPE = "@rbrasier";
const FRAMEWORK_PKGS = ["domain", "shared", "application", "adapters"] as const;

type AiProvider = "anthropic" | "openai" | "mistral";
type AuthMethod = "magic-link" | "pki" | "pki-and-magic-link" | "google-oauth" | "other" | "none";
type DbSetup = "local" | "docker" | "url";

interface ScaffoldOptions {
projectName: string;
appScope: string;
aiProvider: AiProvider;
aiProviderKey: string;
authMethod: AuthMethod;
langfuseEnabled: boolean;
databaseUrl: string;
dbSetup: DbSetup;
Expand Down Expand Up @@ -114,6 +116,21 @@ async function collectInputs(): Promise<ScaffoldOptions> {
initial: "",
});

const { authMethod } = await prompts({
type: "select",
name: "authMethod",
message: "Authentication method",
choices: [
{ title: "Magic link (email, no password)", value: "magic-link" },
{ title: "PKI / client certificate", value: "pki" },
{ title: "PKI + magic link fallback", value: "pki-and-magic-link" },
{ title: "Google OAuth (requires additional setup)", value: "google-oauth" },
{ title: "Other (configure manually)", value: "other" },
{ title: "None (all routes public — dev/internal only)", value: "none" },
],
});
if (!authMethod) process.exit(0);

const { dbInput } = await prompts({
type: "text",
name: "dbInput",
Expand Down Expand Up @@ -193,6 +210,7 @@ async function collectInputs(): Promise<ScaffoldOptions> {
console.log(` Project name : ${pc.cyan(projectName)}`);
console.log(` App scope : ${pc.cyan(appScope)}`);
console.log(` AI provider : ${pc.cyan(aiProvider)}`);
console.log(` Auth method : ${pc.cyan(authMethod)}`);
console.log(` Database : ${pc.cyan(databaseUrl)}`);
console.log(` Admin email : ${pc.cyan(adminEmail)}`);
console.log(` Langfuse : ${pc.cyan(String(langfuseEnabled))}`);
Expand All @@ -212,6 +230,7 @@ async function collectInputs(): Promise<ScaffoldOptions> {
appScope,
aiProvider: aiProvider as AiProvider,
aiProviderKey: aiProviderKey ?? "",
authMethod: authMethod as AuthMethod,
langfuseEnabled,
databaseUrl,
dbSetup,
Expand All @@ -230,7 +249,7 @@ function replaceInFile(filePath: string, find: string, replace: string) {

async function scaffold(opts: ScaffoldOptions) {
const {
projectName, appScope, aiProvider, aiProviderKey,
projectName, appScope, aiProvider, aiProviderKey, authMethod,
langfuseEnabled, databaseUrl, dbSetup, adminEmail, authSecret, targetDir,
} = opts;

Expand Down Expand Up @@ -324,6 +343,7 @@ async function scaffold(opts: ScaffoldOptions) {
BETTER_AUTH_SECRET: authSecret,
ADMIN_SEED_EMAIL: adminEmail,
AI_DEFAULT_PROVIDER: aiProvider,
AUTH_METHOD: authMethod,
OTEL_SERVICE_NAME: `${projectName}-api`,
};

Expand Down Expand Up @@ -404,6 +424,11 @@ try {
console.log(pc.yellow(` ! Add your ${AI_KEY_NAMES[aiProvider]} to .env before starting.`));
console.log();
}
if (authMethod === "none") {
console.log(pc.yellow(" ! Auth method is \"none\" — all /admin/* routes are publicly accessible."));
console.log(pc.yellow(" Do not use this setting in production."));
console.log();
}
console.log(" Push to GitHub:");
console.log(" git remote add origin <url> && git push -u origin main");
console.log();
Expand Down
6 changes: 6 additions & 0 deletions scripts/init-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ echo " 2) pki (client certificate via reverse proxy)"
echo " 3) pki-and-magic-link (PKI primary, magic link fallback)"
echo " 4) google-oauth (Google OAuth — requires additional setup)"
echo " 5) other (configure manually)"
echo " 6) none (no auth — all routes public, dev/internal only)"
prompt "Choice [1]: "
read -r AUTH_CHOICE
case "${AUTH_CHOICE:-1}" in
2) AUTH_METHOD="pki" ;;
3) AUTH_METHOD="pki-and-magic-link" ;;
4) AUTH_METHOD="google-oauth" ;;
5) AUTH_METHOD="other" ;;
6) AUTH_METHOD="none" ;;
*) AUTH_METHOD="magic-link" ;;
esac

Expand Down Expand Up @@ -290,6 +292,10 @@ if [ -f .env.example ]; then
warning "google-oauth requires additional setup. See docs/guides/google-oauth.md."
fi

if [ "$AUTH_METHOD" = "none" ]; then
warning "none auth selected — all /admin/* routes are publicly accessible. Do not use in production."
fi

if [ "$LANGFUSE_ENABLED" = "n" ]; then
info "Langfuse disabled — commenting keys in .env.example…"
sed_inplace "s|^LANGFUSE_|# LANGFUSE_|g" .env.example
Expand Down
Loading