No description
  • Go 60%
  • Svelte 22.5%
  • TypeScript 9.2%
  • CSS 2.8%
  • Dockerfile 1.9%
  • Other 3.6%
Find a file
2026-06-30 23:42:31 +02:00
backend Update Dockerfile to include entrypoint script and adjust user permissions for SQLite database 2026-06-30 23:08:35 +02:00
scripts initial commit 2026-06-30 22:45:15 +02:00
web update 2026-06-30 23:42:31 +02:00
.dockerignore initial commit 2026-06-30 22:45:15 +02:00
.env.example initial commit 2026-06-30 22:45:15 +02:00
.env.production.example initial commit 2026-06-30 22:45:15 +02:00
.gitignore update 2026-06-30 23:42:31 +02:00
Caddyfile initial commit 2026-06-30 22:45:15 +02:00
docker-compose.example.yml Remove legacy Docker Compose files for production and development environments. 2026-06-30 22:53:57 +02:00
docker-compose.production.example.yml Remove legacy Docker Compose files for production and development environments. 2026-06-30 22:53:57 +02:00
Makefile initial commit 2026-06-30 22:45:15 +02:00
package.json initial commit 2026-06-30 22:45:15 +02:00
pnpm-lock.yaml initial commit 2026-06-30 22:45:15 +02:00
pnpm-workspace.yaml initial commit 2026-06-30 22:45:15 +02:00
README.md initial commit 2026-06-30 22:45:15 +02:00

MeshCore Ninja Account MVP

A self-hosted identity foundation for the MeshCore Ninja ecosystem, built with a Go backend, Svelte 5/SvelteKit frontends, Tailwind CSS, and an embedded SQLite database (PostgreSQL optional).

The MVP has two separate applications:

  • account.meshcore.ninja — registration, login, profile, passkeys, linked identities, and session management.
  • auth-test.meshcore.ninja — an independent first-party OIDC client proving that another MeshCore Ninja service can use the account system while keeping its own session.

Included

  • Email magic-link registration and login
  • Discoverable passkeys / usernameless WebAuthn login
  • GitHub OAuth login and explicit account linking
  • Ethereum EOA login and linking with an EIP-4361-style message and EIP-191 signature verification
  • Permanent internal account UUID independent of every login provider
  • Profile editing: username, display name, avatar URL, region/community, and bio
  • Linked identity removal with protection against deleting the last authentication method
  • Passkey management and active-session revocation
  • Minimal first-party OpenID Connect Authorization Code flow with mandatory PKCE S256
  • Ed25519-signed ID and access tokens with discovery and JWKS endpoints
  • Database-agnostic storage (SQLite by default, PostgreSQL optional), Mailpit, Caddy routes, development Compose, and production containers

Architecture

email / passkey / GitHub / Ethereum
                 │
                 ▼
      account.meshcore.ninja
      Go API + account portal
                 │
       OIDC code + PKCE
                 │
                 ▼
     auth-test.meshcore.ninja
       independent app session

Repository layout:

backend/                       Go account API and OIDC issuer
web/account/                   Svelte 5 account portal
web/auth-test/                 Independent SvelteKit OIDC test client
docker-compose.yml             Mailpit (and optional PostgreSQL) for development
docker-compose.production.yml  Full container deployment example
Caddyfile                      Production subdomain routing

Local development

Requirements:

  • Go 1.25+
  • Node.js 22+
  • pnpm via Corepack
  • Docker (for Mailpit; the database is embedded SQLite by default)
cp .env.example .env
docker compose up -d mailpit
corepack enable
pnpm install

The backend creates and migrates the SQLite database on first run. To use PostgreSQL instead, set DATABASE_URL=postgres://… in .env and start the optional container with docker compose --profile postgres up -d postgres.

Run the backend:

set -a
source .env
set +a
make backend

Run both frontends in another terminal:

make web

Open:

  • Account portal: http://localhost:5173
  • Auth test client: http://localhost:5174
  • Mailpit: http://localhost:8025

The account Vite server proxies /api, /oauth, /.well-known, and /healthz to the Go service on port 8080.

First login

Without GitHub configuration, start with email:

  1. Enter an email at http://localhost:5173/login.
  2. Open Mailpit at http://localhost:8025.
  3. Follow the one-time login link.
  4. Add a passkey from Security.
  5. Open http://localhost:5174 and test the independent OIDC login.

DEV_EXPOSE_MAGIC_LINK=true also shows the development login URL directly in the account UI. Disable this outside local development.

GitHub OAuth

Create a GitHub OAuth App:

Homepage URL:            http://localhost:5173
Authorization callback: http://localhost:5173/api/auth/github/callback

Set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET in .env.

Production callback:

https://account.meshcore.ninja/api/auth/github/callback

GitHub login proves identity only. Future automatic PR creation should use a separate MeshCore Ninja GitHub App and explicit permissions rather than reusing the login credential.

Passkeys

WebAuthn works on HTTPS origins and on localhost.

Development:

WEBAUTHN_RP_ID=localhost
WEBAUTHN_ORIGINS=http://localhost:5173

Production:

WEBAUTHN_RP_ID=meshcore.ninja
WEBAUTHN_ORIGINS=https://account.meshcore.ninja

The MVP requests discoverable credentials and required user verification. Passkey ceremonies are completed only by the account origin.

Ethereum login

The browser requests a wallet signature over a server-generated, short-lived sign-in message containing the account domain, URI, chain ID, nonce, issue time, and expiration. The Go backend verifies the recovered EOA address and consumes the challenge once.

The MVP supports normal externally owned accounts. Contract-wallet verification through ERC-1271 is intentionally outside the initial scope.

OIDC test client

The account service exposes:

/.well-known/openid-configuration
/.well-known/jwks.json
/oauth/authorize
/oauth/token
/oauth/userinfo

The initial client is intentionally fixed:

client_id:    meshcore-ninja-auth-test
redirect_uri: http://localhost:5174/callback
flow:         Authorization Code + PKCE S256
scopes:       openid profile email

auth-test.meshcore.ninja validates the ID token against the issuer JWKS, checks issuer, audience and nonce, and then creates a separate HttpOnly local session cookie. ACCOUNT_BACKCHANNEL_URL may point token and JWKS requests at the internal Go service while the public issuer remains https://account.meshcore.ninja.

Signing key

Development generates an ephemeral OIDC key when none is supplied. Generate a persistent production key with:

make keygen

Put the printed base64url value into OIDC_ED25519_PRIVATE_KEY. Store it as a secret and back it up securely.

Tests and checks

make test

This runs Go tests and Svelte checks. A PKCE RFC test vector and internal redirect-safety tests are included.

Production example

cp .env.production.example .env
# Fill all secrets and SMTP/GitHub settings.
docker compose -f docker-compose.production.yml up -d --build

Point DNS for both subdomains to the Caddy host:

account.meshcore.ninja
auth-test.meshcore.ninja

Security boundary

This MVP deliberately avoids a shared parent-domain login cookie. The account service owns its own session; every client receives a short-lived authorization code and creates its own secure session. All application data should reference the internal account UUID, never an email, GitHub ID, passkey credential ID, or Ethereum address.

Before public production use, commission an independent security review and add distributed rate limiting, structured audit events, transactional email delivery with TLS/authentication, key rotation, backup and restore drills, abuse controls, and browser-level end-to-end tests. The OIDC surface is deliberately narrow and intended only for registered first-party clients; it is not a general-purpose public identity provider.