Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d5de21b
dev(db): Adds a dev user to the seeding script for API
AlexAxthelm Feb 5, 2026
0f9d57a
style: ruff
AlexAxthelm Feb 5, 2026
3aa0d05
feat(auth): add stitch-auth package for OIDC JWT validation
mbarlow12 Feb 10, 2026
a62e20d
build: add stitch-auth to workspace members
mbarlow12 Feb 10, 2026
4301aba
fix(lock): regenerate uv.lock without API stitch-auth dep
mbarlow12 Feb 10, 2026
967d862
build(api): add stitch-auth dependency
mbarlow12 Feb 10, 2026
296271e
feat(api): add sub column to User, consolidate first_name/last_name t…
mbarlow12 Feb 10, 2026
bbebbb8
feat(api): integrate JWT auth with JIT user provisioning
mbarlow12 Feb 10, 2026
387ccd4
infra: add stitch-auth to Docker build, AUTH_DISABLED for local dev
mbarlow12 Feb 10, 2026
cc17a25
fix(stitch.api.db): improve type hints & LBYL in uow
mbarlow12 Feb 10, 2026
3eeaa1e
refactor(api): move auth code from deps.py to stitch.api.auth module
mbarlow12 Feb 10, 2026
7c39caa
build(lock): update uv.lock for stitch-auth API dependency
mbarlow12 Feb 10, 2026
5bd9155
ci: trigger workflow run after base branch change
mbarlow12 Feb 10, 2026
5ed8066
feat(auth): add local auth testing with localauth0 mock OIDC server
mbarlow12 Feb 10, 2026
dd2099c
md,yml formatting and localauth0 config
mbarlow12 Feb 10, 2026
267bc09
fix(auth): add access_token custom claims, auth-demo script, and docs…
mbarlow12 Feb 11, 2026
407bba4
fix(auth): handle garbage tokens in validator, add demo script confir…
mbarlow12 Feb 11, 2026
5a2dd60
ci: trigger workflow run after base branch change
mbarlow12 Feb 11, 2026
0d3d731
feat: add auth0 npm package
mbarlow12 Feb 18, 2026
d8c7b40
Merge branch 'main' into dev/local-auth-w-docker-fixes
mbarlow12 Feb 19, 2026
4626d39
chore(frontend): add .env.example and .env to .gitignore
mbarlow12 Feb 19, 2026
498db09
test(frontend): add Auth0 env vars to vitest config
mbarlow12 Feb 19, 2026
973c86e
test(frontend): add global Auth0 mock to test setup
mbarlow12 Feb 19, 2026
7ed9191
feat(frontend): implement authenticated fetcher with tests
mbarlow12 Feb 19, 2026
cae5cd3
feat(frontend): update API functions to accept fetcher parameter
mbarlow12 Feb 19, 2026
50748b4
feat(frontend): add useAuthenticatedQuery hook and wire up auth to qu…
mbarlow12 Feb 19, 2026
0d74999
feat(frontend): implement AuthGate component with tests
mbarlow12 Feb 19, 2026
ab622c5
feat(frontend): configure Auth0Provider, add AuthGate and LogoutButto…
mbarlow12 Feb 19, 2026
77d4a97
test(frontend): add env config tests
mbarlow12 Feb 19, 2026
6e8975f
feat(frontend): add LogoutButton component and env config module
mbarlow12 Feb 19, 2026
e70cc87
chore: move .env.example to repo root, remove redundant frontend .env…
mbarlow12 Feb 19, 2026
a5ed202
test(frontend): add LogoutButton tests, extract shared auth0 mock def…
mbarlow12 Feb 20, 2026
150103f
chore: prettier formatting
mbarlow12 Feb 20, 2026
0c770fb
Merge branch 'dev/local-auth-w-docker-fixes' into feat/auth-ui-locala…
mbarlow12 Feb 20, 2026
d2771e3
chore: rename env.example and add comments
mbarlow12 Feb 20, 2026
056b4b5
feat(frontend): add LoginPage component, replace auto-redirect in Aut…
mbarlow12 Feb 21, 2026
0671657
infra: add CORS proxy for localauth0, pass build args to frontend Doc…
mbarlow12 Feb 21, 2026
90d3e6b
fix(frontend): use openUrl for logout redirect, bypass Auth0 /v2/logout
mbarlow12 Feb 21, 2026
249fba9
fix(docker): OpenResty nonce proxy, fix client_id, faster healthcheck
mbarlow12 Feb 21, 2026
878581d
docs: add local auth proxy guide, update auth-testing references
mbarlow12 Feb 21, 2026
fd59a59
fix(ci): use correct .env.example filename in Docker workflows
mbarlow12 Feb 21, 2026
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
45 changes: 45 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
LOG_LEVEL=info

POSTGRES_DB=stitch
POSTGRES_HOST=db
POSTGRES_PORT=5432

STITCH_MIGRATOR_PASSWORD=CHANGE_ME_migrator123!
STITCH_APP_PASSWORD=CHANGE_ME_app123!

STITCH_DB_SCHEMA_MODE="if-empty"
STITCH_DB_SEED_MODE="if-needed"
STITCH_DB_SEED_PROFILE="dev"

FRONTEND_ORIGIN_URL=http://localhost:3000

# --- Auth (API) ---
# AUTH_DISABLED=true bypasses JWT validation for local dev (default).
# When false, the API validates Bearer tokens using the issuer/audience/JWKS below.
AUTH_DISABLED=true

# For local auth testing with mock OIDC (docker compose --profile auth-test):
# AUTH_DISABLED=false
# AUTH_ISSUER=http://localhost:3100/
# AUTH_AUDIENCE=stitch-api-local
# AUTH_JWKS_URI=http://localauth0:3000/.well-known/jwks.json
#
# For real Auth0:
# AUTH_ISSUER=https://<tenant>.auth0.com/
# AUTH_AUDIENCE=<your-api-identifier>
# AUTH_JWKS_URI=https://<tenant>.auth0.com/.well-known/jwks.json

# --- Auth (Frontend) ---
#
# For local auth testing (localauth0 via CORS proxy on port 3100):
# VITE_AUTH0_DOMAIN=http://localhost:3100
# localauth0 hardcodes expected client_id as the literal string "client_id"
# VITE_AUTH0_CLIENT_ID=client_id
# VITE_AUTH0_AUDIENCE=stitch-api-local
# VITE_API_URL=http://localhost:8000/api/v1
#
# For real Auth0:
# VITE_AUTH0_DOMAIN=<tenant>.auth0.com
# VITE_AUTH0_CLIENT_ID=<spa-client-id>
# VITE_AUTH0_AUDIENCE=<api-identifier>
# VITE_API_URL=http://localhost:8000/api/v1
2 changes: 1 addition & 1 deletion .github/workflows/docker-compose-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ jobs:
uses: actions/checkout@v5

- name: Set up example Env file
run: cp env.example .env
run: cp .env.example .env

- run: docker compose build
2 changes: 1 addition & 1 deletion .github/workflows/docker-compose-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ jobs:
uses: actions/checkout@v5

- name: Set up example Env file
run: cp env.example .env
run: cp .env.example .env

- run: docker compose config
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,45 @@ Note: The `db-init` service runs automatically (via `depends_on`) to apply schem
- `STITCH_DB_SEED_MODE`
- `STITCH_DB_SEED_PROFILE`

### Auth Testing (optional)

By default, auth is disabled (`AUTH_DISABLED=true`) — all requests get a hardcoded dev user with no token required. To test real JWT auth flows locally with a mock OIDC server:

1. Update `.env`:
```
AUTH_DISABLED=false
AUTH_ISSUER=http://localauth0:3000/
AUTH_AUDIENCE=stitch-api-local
AUTH_JWKS_URI=http://localauth0:3000/.well-known/jwks.json
```

2. Start with the `auth-test` profile:
```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml --profile auth-test up --build
```

3. Get a token and make requests:
```bash
# Health check (always open)
curl localhost:8000/api/v1/health

# No token → 401
curl localhost:8000/api/v1/resources/

# Get a valid token
TOKEN=$(curl -s -X POST localhost:3100/oauth/token \
-H "Content-Type: application/json" \
-d '{"client_id":"client_id","client_secret":"client_secret","audience":"stitch-api-local","grant_type":"client_credentials"}' \
| jq -r '.access_token')

# Authenticated request → 200
curl -H "Authorization: Bearer $TOKEN" localhost:8000/api/v1/resources/
```

Swagger UI (`/docs`) also supports the "Authorize" button for token entry.

See [docs/auth-testing.md](docs/auth-testing.md) for the full scenario guide.

## Reset (wipe DB volumes safely)

Stop containers and delete the Postgres volume (this removes all local DB data):
Expand Down
4 changes: 4 additions & 0 deletions deployments/stitch-frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ COPY index.html vite.config.js ./
COPY public/ ./public/
COPY src/ ./src/

ARG VITE_AUTH0_DOMAIN
ARG VITE_AUTH0_CLIENT_ID
ARG VITE_AUTH0_AUDIENCE
ARG VITE_API_URL
RUN npm run build

########################
Expand Down
98 changes: 98 additions & 0 deletions deployments/stitch-frontend/package-lock.json

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

1 change: 1 addition & 0 deletions deployments/stitch-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"test:coverage": "vitest --coverage"
},
"dependencies": {
"@auth0/auth0-react": "^2.15.0",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-query": "^5.90.16",
"react": "^19.2.0",
Expand Down
4 changes: 4 additions & 0 deletions deployments/stitch-frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import ResourcesView from "./components/ResourcesView";
import ResourceView from "./components/ResourceView";
import { LogoutButton } from "./components/LogoutButton";

function App() {
return (
<div className="min-h-screen w-screen bg-gray-100 p-8">
<div className="max-w-4xl mx-auto flex justify-end mb-4">
<LogoutButton />
</div>
<ResourcesView endpoint="/api/v1/resources" />
<ResourceView className="mt-24" endpoint="/api/v1/resources/{id}" />
</div>
Expand Down
30 changes: 30 additions & 0 deletions deployments/stitch-frontend/src/auth/AuthGate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useAuth0 } from "@auth0/auth0-react";
import LoginPage from "../components/LoginPage";

export default function AuthGate({ children }) {
const { isLoading, isAuthenticated, error } = useAuth0();

if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<p className="text-gray-500 text-lg">Loading...</p>
</div>
);
}

if (error) {
return (
<div className="min-h-screen flex items-center justify-center">
<p className="text-red-600 text-lg">
Authentication error: {error.message}
</p>
</div>
);
}

if (!isAuthenticated) {
return <LoginPage />;
}

return children;
}
62 changes: 62 additions & 0 deletions deployments/stitch-frontend/src/auth/AuthGate.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { useAuth0 } from "@auth0/auth0-react";
import { auth0TestDefaults } from "../test/utils";
import AuthGate from "./AuthGate";

describe("AuthGate", () => {
it("shows loading indicator while auth is loading", () => {
vi.mocked(useAuth0).mockReturnValue({
...auth0TestDefaults,
isLoading: true,
isAuthenticated: false,
});

render(
<AuthGate>
<div>App Content</div>
</AuthGate>,
);

expect(screen.getByText("Loading...")).toBeInTheDocument();
expect(screen.queryByText("App Content")).not.toBeInTheDocument();
});

it("shows error message when auth fails", () => {
vi.mocked(useAuth0).mockReturnValue({
...auth0TestDefaults,
isAuthenticated: false,
error: new Error("Something went wrong"),
});

render(
<AuthGate>
<div>App Content</div>
</AuthGate>,
);

expect(
screen.getByText("Authentication error: Something went wrong"),
).toBeInTheDocument();
expect(screen.queryByText("App Content")).not.toBeInTheDocument();
});

it("renders LoginPage when unauthenticated", () => {
vi.mocked(useAuth0).mockReturnValue({
...auth0TestDefaults,
isAuthenticated: false,
});

render(
<AuthGate>
<div>App Content</div>
</AuthGate>,
);

expect(screen.getByText("Stitch")).toBeInTheDocument();
expect(
screen.getByRole("button", { name: /log in to continue/i }),
).toBeInTheDocument();
expect(screen.queryByText("App Content")).not.toBeInTheDocument();
});
});
Loading