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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ SETUP_TOKEN=
REQUIRE_ADMIN_APPROVAL=true # true = new users start as PENDING, admin must approve
LOGIN_THROTTLE_LIMIT=5 # max login attempts per window
LOGIN_THROTTLE_TTL_MS=10000 # window duration in ms (default: 10s)
# Optional. Enables Google ID-token login; use the trusted frontend OAuth client ID.
# The backend derives its frontend origin from CORS_ORIGIN and never accepts an origin from requests.
GOOGLE_CLIENT_ID=

# ─── Docker / Host paths ──────────────────────────────────────────────────────
# DOCKER_SOCKET here names the HOST socket path, used as the Compose bind
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

> **Phase 1 - v1.0 release candidate.** Auth (JWT cookies, 2FA, admin approval), transactional first-admin bootstrap, protocol-1 capability discovery, server lifecycle, host metrics over WebSocket, and the one-command Docker deployment (Caddy auto-HTTPS, Drizzle migrations on boot, multi-arch images on GHCR) are shipped and CI-green. Open decisions and the hardening backlog are tracked in [SPEC.md](./SPEC.md) §16/§19.
>
> **Phase 1.5 Round 1 - authorization spine shipped.** Per-server visibility (`OPEN`/`REQUEST`/`PRIVATE`), request/approval workflows, and MOD granular permissions (`PermissionsGuard` + `mod_permissions`) are live. OAuth, Minecraft linking, magic links, and invites are planned for later Phase 1.5 rounds - see the [roadmap](https://minepanel.xyz/#roadmap).
> **Phase 1 - authorization spine shipped.** Per-server visibility (`OPEN`/`REQUEST`/`PRIVATE`), request/approval workflows, and MOD granular permissions (`PermissionsGuard` + `mod_permissions`) are live.
>
> **Phase 1.5 - Google OAuth shipped.** Challenge-bound Google login, local JWKS verification, LinkConfirmationRequired linking semantics, and truthful capability discovery (`capabilities.auth.googleOAuth`) are live. GitHub OAuth, Minecraft linking, magic links, and invites remain planned - see the [roadmap](https://minepanel.xyz/#roadmap).

---

Expand Down Expand Up @@ -173,7 +175,7 @@ Full docs at `/docs` (Swagger UI) when the server is running.
| Group | Endpoints |
|------------|-----------------------------------------------------------------------------------------------|
| Setup | `GET /setup/status` · `POST /setup/init` (requires `X-Setup-Token`, throttled 5/10 min/IP) |
| Auth | `POST /auth/register` · `POST /auth/login` · `POST /auth/refresh` · `POST /auth/logout` · `POST /auth/logout-all` · `GET /auth/profile` · `GET /auth/sessions` · `PATCH /auth/profile` · `PATCH /auth/password` · `POST /auth/2fa/setup` · `POST /auth/2fa/confirm` · `POST /auth/2fa/verify` · `DELETE /auth/2fa/disable` |
| Auth | `POST /auth/register` · `POST /auth/login` · `POST /auth/refresh` · `POST /auth/logout` · `POST /auth/logout-all` · `GET /auth/profile` · `GET /auth/sessions` · `PATCH /auth/profile` · `PATCH /auth/password` · `POST /auth/oauth/challenge` · `POST /auth/oauth/google/login` · `POST /auth/oauth/google/link` · `POST /auth/2fa/setup` · `POST /auth/2fa/confirm` · `POST /auth/2fa/verify` · `DELETE /auth/2fa/disable` |
| Admin | `GET /admin/users` · `PATCH /admin/users/:id/status` · `PATCH /admin/users/:id/role` · `POST /admin/users/:id/reset-password` · `DELETE /admin/users/:id/2fa` · `GET/POST /admin/users/:id/permissions` · `DELETE /admin/users/:id/permissions/:permId` |
| Health | `GET /health` |
| Info | `GET /api/info` - protocol-1 capability discovery (`Cache-Control: no-store`) |
Expand All @@ -192,4 +194,6 @@ See [SPEC.md](./SPEC.md) for the full architecture specification.

## License

MIT — see [LICENSE](./LICENSE).

Not affiliated with Mojang Studios or Microsoft. Minecraft is a trademark of Mojang Synergies AB.
79 changes: 45 additions & 34 deletions SPEC.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions drizzle/0004_high_yellow_claw.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Phase C session substrate: legacy rows store bcrypt hashes of old-format tokens
-- with no derivable jti, so they cannot be verified under the new scheme.
-- Deleting them forces re-login (documented session-invalidation consequence).
ALTER TABLE "refresh_tokens" DROP CONSTRAINT "refresh_tokens_token_unique";--> statement-breakpoint
DELETE FROM "refresh_tokens";--> statement-breakpoint
ALTER TABLE "refresh_tokens" DROP COLUMN "token";--> statement-breakpoint
ALTER TABLE "refresh_tokens" ADD COLUMN "token_id_hash" text NOT NULL;--> statement-breakpoint
CREATE INDEX "refresh_tokens_user_id_idx" ON "refresh_tokens" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "refresh_tokens_expires_at_idx" ON "refresh_tokens" USING btree ("expires_at");--> statement-breakpoint
ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_token_id_hash_unique" UNIQUE("token_id_hash");
25 changes: 25 additions & 0 deletions drizzle/0005_username_lowercase_normalization.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- Phase D identity normalization (D-10, adopted): canonical lowercase usernames.
-- Preflight: a case-insensitive duplicate (e.g. `Bob` + `bob`) cannot be
-- migrated without manual resolution — fail loudly, never silently merge or
-- drop a row. Clean databases are unchanged by the preflight.
DO $$
DECLARE
conflicting_count integer;
BEGIN
SELECT count(*) INTO conflicting_count
FROM (
SELECT lower(username) AS canon
FROM users
GROUP BY lower(username)
HAVING count(*) > 1
) conflicts;

IF conflicting_count > 0 THEN
RAISE EXCEPTION
'Username case-collision detected: % lowercase-canonical value(s) map to multiple accounts; resolve manually before applying migration 0005',
conflicting_count;
END IF;
END $$;--> statement-breakpoint
-- Canonicalize all existing usernames; the unique(username) constraint is
-- satisfied because the preflight proved injectivity of lower(username).
UPDATE users SET username = lower(username);
17 changes: 17 additions & 0 deletions drizzle/0006_aspiring_azazel.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE "oauth_challenges" (
"id" text PRIMARY KEY NOT NULL,
"provider" text NOT NULL,
"challenge_hash" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "oauth_challenges_challenge_hash_unique" UNIQUE("challenge_hash"),
CONSTRAINT "oauth_challenges_provider_check" CHECK ("oauth_challenges"."provider" = 'google')
);
--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "password_hash" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "google_id" text;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "github_id" text;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "minecraft_verified" boolean DEFAULT false NOT NULL;--> statement-breakpoint
CREATE INDEX "oauth_challenges_expires_at_idx" ON "oauth_challenges" USING btree ("expires_at");--> statement-breakpoint
ALTER TABLE "users" ADD CONSTRAINT "users_google_id_unique" UNIQUE("google_id");--> statement-breakpoint
ALTER TABLE "users" ADD CONSTRAINT "users_github_id_unique" UNIQUE("github_id");
Loading