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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- **Configurable route prefix** — new `route_prefix` config key (`NEEV_ROUTE_PREFIX`, default `neev`) namespaces every machine-facing route the package registers: the API namespace, OAuth redirect/callback, tenant SSO, and `/csrf-cookie`. Blade UI pages (`/login`, `/account/...`) stay at the root. Route names are unchanged. The MFA-token route gate in `NeevAPIMiddleware` now follows the prefix (previously hardcoded — customised route files silently broke MFA step-up)

### Changed
- **BREAKING: OAuth and tenant-SSO routes moved under the route prefix** — `/oauth/{service}[/callback]` → `/neev/oauth/{service}[/callback]`, `/sso/redirect|callback` → `/neev/sso/...`, and `/api/tenant/auth` → `/neev/tenant/auth`. **Update the redirect URIs registered with your identity providers** (Entra/Google/Okta app registrations, OAuth apps)
- **SPA cookie mode — phase 1 (plumbing)** — same-origin SPAs can now authenticate via an HttpOnly cookie instead of JS-stored bearer tokens:
- `EnsureSpaRequestsAreStateful` middleware (in the `neev:api` and `neev:login` groups): for requests from a configured stateful origin, validates a signed double-submit CSRF token on state-changing methods (419 on failure) and promotes the auth cookie to an `Authorization: Bearer` header; a no-op for everything else, so existing bearer callers are untouched
- `GET /neev/csrf-cookie` issues the CSRF cookie; the token is HMAC-signed to the app key (defeats subdomain cookie injection, no server-side state)
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,14 @@ Enable in configuration:
```

Redirect URLs:
- `GET /oauth/{service}` - Redirect to provider
- `GET /oauth/{service}/callback` - Handle callback
- `GET /neev/oauth/{service}` - Redirect to provider
- `GET /neev/oauth/{service}/callback` - Handle callback

---

## API Reference

All API routes are prefixed with `/neev`. Include the Bearer token for authenticated endpoints.
All API routes are prefixed with `/neev` — the prefix is configurable via `route_prefix` in `config/neev.php` (env `NEEV_ROUTE_PREFIX`). Include the Bearer token for authenticated endpoints.

### Authentication Endpoints

Expand Down Expand Up @@ -349,10 +349,10 @@ All API routes are prefixed with `/neev`. Include the Bearer token for authentic
| POST | `/update-password` | `user-password.update` | Process reset |
| GET | `/otp/mfa/{method}` | `otp.mfa.create` | MFA verification |
| POST | `/otp/mfa` | `otp.mfa.store` | Verify MFA code |
| GET | `/oauth/{service}` | `oauth.redirect` | OAuth redirect |
| GET | `/oauth/{service}/callback` | `oauth.callback` | OAuth callback |
| GET | `/sso/redirect` | `sso.redirect` | Tenant SSO redirect |
| GET | `/sso/callback` | `sso.callback` | Tenant SSO callback |
| GET | `/neev/oauth/{service}` | `oauth.redirect` | OAuth redirect |
| GET | `/neev/oauth/{service}/callback` | `oauth.callback` | OAuth callback |
| GET | `/neev/sso/redirect` | `sso.redirect` | Tenant SSO redirect |
| GET | `/neev/sso/callback` | `sso.callback` | Tenant SSO callback |

### Authenticated Routes (neev:web middleware)

Expand Down Expand Up @@ -533,7 +533,7 @@ php artisan neev:auth:show # Show current auth settings
### Get Tenant Auth Config

```bash
curl -X GET https://acme.yourapp.com/api/tenant/auth
curl -X GET https://acme.yourapp.com/neev/tenant/auth
```

Response:
Expand All @@ -542,7 +542,7 @@ Response:
"auth_method": "sso",
"sso_enabled": true,
"sso_provider": "entra",
"sso_redirect_url": "https://acme.yourapp.com/sso/redirect"
"sso_redirect_url": "https://acme.yourapp.com/neev/sso/redirect"
}
```

Expand Down
20 changes: 20 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ changes see [CHANGELOG.md](./CHANGELOG.md).

## 0.4.5 → Unreleased

**OAuth/SSO routes moved under the route prefix (action required for
identity providers).**
All machine-facing routes now live under the configurable
`route_prefix` (default `neev`):

| Old path | New path |
|---|---|
| `/oauth/{service}` and `/oauth/{service}/callback` (web) | `/neev/oauth/{service}[/callback]` |
| `/sso/redirect`, `/sso/callback` | `/neev/sso/redirect`, `/neev/sso/callback` |
| `/api/tenant/auth` | `/neev/tenant/auth` |

- **Update the redirect URIs registered at your identity providers**
(Microsoft Entra, Google, Okta app registrations and OAuth apps) to
the new callback URLs.
- Apps that published `routes/neev.php` keep their published copy —
re-publish or re-apply your customisations to pick up the prefix.
- To rename the namespace (e.g. `/auth/...`), set
`NEEV_ROUTE_PREFIX=auth`. Route *names* (`neev.*` etc.) are
unchanged either way.

**Authenticator MFA setup now requires verification (behaviour change).**
Adding an authenticator creates it in `pending` status; the user must
submit a valid TOTP (API: `POST /neev/mfa/setup/verify`; web: the
Expand Down
18 changes: 18 additions & 0 deletions config/neev.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@
// Team sub-grouping. Optional in both tenant and non-tenant modes.
'team' => false,

/*
|--------------------------------------------------------------------------
| Routes
|--------------------------------------------------------------------------
|
| URL prefix for every machine-facing route the package registers: the
| API namespace, OAuth redirect/callback, and tenant SSO endpoints
| (e.g. 'auth' gives /auth/login, /auth/oauth/{service}/callback,
| /auth/sso/callback). Blade UI pages (/login, /account/...) stay at
| the root — they are end-user URLs, not part of the API namespace.
|
| Changing this also changes the OAuth/SSO callback URLs registered
| with your identity providers — update those app registrations too.
| Route NAMES are unaffected.
|
*/
'route_prefix' => env('NEEV_ROUTE_PREFIX', 'neev'),

/*
|--------------------------------------------------------------------------
| Authentication
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# API Reference

Complete reference for all Neev API endpoints. All API routes are prefixed with `/neev`.
Complete reference for all Neev API endpoints. All API routes are prefixed with the configurable route prefix — `route_prefix` in `config/neev.php` (env `NEEV_ROUTE_PREFIX`), default `neev`. This documentation uses the default `/neev` prefix throughout.

---

Expand Down
12 changes: 7 additions & 5 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ Authenticate via third-party providers.
```env
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI="${APP_URL}/oauth/google/callback"
GOOGLE_REDIRECT_URI="${APP_URL}/neev/oauth/google/callback"
```

### Security Note
Expand All @@ -382,8 +382,10 @@ OAuth and Social login bypasses Neev's MFA requirements and password policies. U

### URLs

- **Redirect:** `GET /oauth/{provider}`
- **Callback:** `GET /oauth/{provider}/callback`
- **Redirect:** `GET /neev/oauth/{provider}`
- **Callback:** `GET /neev/oauth/{provider}/callback`

The `/neev` prefix is configurable via `route_prefix` in `config/neev.php` (env `NEEV_ROUTE_PREFIX`). Changing it also changes the callback URLs registered with your OAuth providers.

---

Expand Down Expand Up @@ -433,7 +435,7 @@ $tenant->authSettings()->create([
### API Endpoint

```http
GET /api/tenant/auth
GET /neev/tenant/auth
```

Returns tenant auth configuration:
Expand All @@ -443,7 +445,7 @@ Returns tenant auth configuration:
"auth_method": "sso",
"sso_enabled": true,
"sso_provider": "entra",
"sso_redirect_url": "https://acme.yourapp.com/sso/redirect"
"sso_redirect_url": "https://acme.yourapp.com/neev/sso/redirect"
}
```

Expand Down
20 changes: 20 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ The `neev:install` wizard asks exactly these two questions and sets the flags fo

---

## Routes

### Route Prefix

```php
'route_prefix' => env('NEEV_ROUTE_PREFIX', 'neev'),
```

URL prefix for every machine-facing route the package registers: the API namespace (including `/csrf-cookie`), the OAuth redirect/callback routes, and the tenant SSO endpoints. For example, setting it to `auth` gives `/auth/login`, `/auth/oauth/{service}/callback`, and `/auth/sso/callback`.

Blade UI pages (`/login`, `/register`, `/account/...`) stay at the root — they are end-user URLs, not part of the API namespace.

> **Warning:** Changing the prefix also changes the OAuth/SSO callback URLs registered with your identity providers — update those app registrations too. Route names are unaffected.

---

## Authentication

### Username Support
Expand Down Expand Up @@ -352,6 +368,9 @@ return [
'tenant' => false,
'team' => true,

// Routes
'route_prefix' => env('NEEV_ROUTE_PREFIX', 'neev'),

// Authentication
'support_username' => false,
'oauth' => [
Expand Down Expand Up @@ -436,6 +455,7 @@ return [

| Variable | Description | Default |
|----------|-------------|---------|
| `NEEV_ROUTE_PREFIX` | Prefix for machine-facing routes (API, OAuth, SSO, csrf-cookie) | `neev` |
| `NEEV_JWT_SECRET` | Secret for signing MFA JWTs | Falls back to `APP_KEY` |
| `MAXMIND_EDITION` | GeoIP database edition | `GeoLite2-City` |
| `MAXMIND_LICENSE_KEY` | MaxMind license key | - |
Expand Down
1 change: 1 addition & 0 deletions docs/design-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Worked examples of the layers in action, for calibration:
|---|---|---|
| MFA pending → active requires OTP proof in shipped flows; no config toggle | 1 | A toggle would be a footgun; the flow is what neev is responsible for |
| `MultiFactorAuth::activate()` exists for programmatic activation | 4 | Legitimate bypass, explicit in app code, keeps event + preferred invariants |
| Route prefix is a config value (`route_prefix`, default `neev`), not hardcoded to `auth` | 2 | User-visible string real apps differ on (Sanctum/Fortify precedent); defaulting to `auth` would be collision-prone and breaking, and "publish the routes file" was a trap — the MFA route gate hardcoded the old prefix |
| `login_throttle` delays are configurable numbers | 2 | Apps differ on tolerance; it's a value, not a branch |
| Email verification enforcement is a middleware alias, not automatic | 3 | Which routes require it is product policy; the mechanism is fully shipped |
| Push notifications rejected from the package (PR #25) | — | Fails the litmus test; events give the app everything it needs |
Expand Down
10 changes: 6 additions & 4 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,14 @@ Update configuration:

## OAuth Provider Setup

The callback URLs below use the default route prefix (`route_prefix` in `config/neev.php`, env `NEEV_ROUTE_PREFIX`, default `neev`). If you change the prefix, update the redirect URIs registered with each provider accordingly.

### Google

1. Create a project in [Google Cloud Console](https://console.cloud.google.com/)
2. Enable Google+ API
3. Create OAuth 2.0 credentials
4. Add redirect URI: `https://yourapp.com/oauth/google/callback`
4. Add redirect URI: `https://yourapp.com/neev/oauth/google/callback`

```php
// config/services.php
Expand All @@ -309,13 +311,13 @@ Update configuration:
```env
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI="${APP_URL}/oauth/google/callback"
GOOGLE_REDIRECT_URI="${APP_URL}/neev/oauth/google/callback"
```

### GitHub

1. Create an OAuth App in [GitHub Developer Settings](https://github.com/settings/developers)
2. Add callback URL: `https://yourapp.com/oauth/github/callback`
2. Add callback URL: `https://yourapp.com/neev/oauth/github/callback`

```php
// config/services.php
Expand All @@ -329,7 +331,7 @@ GOOGLE_REDIRECT_URI="${APP_URL}/oauth/google/callback"
### Microsoft

1. Register an app in [Azure Portal](https://portal.azure.com/)
2. Add redirect URI: `https://yourapp.com/oauth/microsoft/callback`
2. Add redirect URI: `https://yourapp.com/neev/oauth/microsoft/callback`

```php
// config/services.php
Expand Down
20 changes: 11 additions & 9 deletions docs/multi-tenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ $tenant->authSettings()->create([
Azure App Registration:
1. Go to Azure Portal > App Registrations
2. Create new registration
3. Add redirect URI: `https://tenant.yourapp.com/sso/callback`
3. Add redirect URI: `https://tenant.yourapp.com/neev/sso/callback`
4. Generate client secret
5. Note the Application (client) ID and Directory (tenant) ID

Expand Down Expand Up @@ -532,7 +532,7 @@ $tenant->authSettings()->create([
### 1. Get Tenant Auth Config

```bash
curl -X GET https://acme.yourapp.com/api/tenant/auth
curl -X GET https://acme.yourapp.com/neev/tenant/auth
```

**Response:**
Expand All @@ -542,14 +542,14 @@ curl -X GET https://acme.yourapp.com/api/tenant/auth
"auth_method": "sso",
"sso_enabled": true,
"sso_provider": "entra",
"sso_redirect_url": "https://acme.yourapp.com/sso/redirect"
"sso_redirect_url": "https://acme.yourapp.com/neev/sso/redirect"
}
```

### 2. Redirect to SSO

```http
GET /sso/redirect?email=user@acme.com
GET /neev/sso/redirect?email=user@acme.com
```

User is redirected to the identity provider.
Expand All @@ -559,7 +559,7 @@ User is redirected to the identity provider.
After authentication, the user is redirected to:

```http
GET /sso/callback?code=auth-code&state=...
GET /neev/sso/callback?code=auth-code&state=...
```

Neev:
Expand All @@ -573,7 +573,7 @@ Neev:
For single-page applications, pass a `redirect_uri`:

```http
GET /sso/redirect?redirect_uri=https://acme.yourapp.com/app
GET /neev/sso/redirect?redirect_uri=https://acme.yourapp.com/app
```

After SSO, user is redirected with the token in the URL fragment (not query parameter) to prevent server-side logging:
Expand Down Expand Up @@ -680,14 +680,16 @@ $ssoManager->ensureMembership($user, $tenant);

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/tenant/auth` | Get tenant auth config |
| GET | `/neev/tenant/auth` | Get tenant auth config |

### SSO

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/sso/redirect` | Initiate SSO flow |
| GET | `/sso/callback` | Handle SSO callback |
| GET | `/neev/sso/redirect` | Initiate SSO flow |
| GET | `/neev/sso/callback` | Handle SSO callback |

> The `/neev` prefix on these endpoints is configurable via `route_prefix` in `config/neev.php` (env `NEEV_ROUTE_PREFIX`).

---

Expand Down
12 changes: 8 additions & 4 deletions docs/web-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ These routes are accessible without authentication.

### OAuth / Social Login

These routes live under the configurable route prefix (`route_prefix` in `config/neev.php`, env `NEEV_ROUTE_PREFIX`, default `neev`). The paths below use the default prefix.

| Method | Route | Name | Description |
|--------|-------|------|-------------|
| GET | `/oauth/{service}` | `oauth.redirect` | Redirect to OAuth provider |
| GET | `/oauth/{service}/callback` | `oauth.callback` | Handle OAuth callback |
| GET | `/neev/oauth/{service}` | `oauth.redirect` | Redirect to OAuth provider |
| GET | `/neev/oauth/{service}/callback` | `oauth.callback` | Handle OAuth callback |

**URL Parameters:**
- `service` - OAuth provider (`google`, `github`, `microsoft`, `apple`)
Expand All @@ -91,10 +93,12 @@ These routes are accessible without authentication.

### Tenant SSO

These routes also live under the configurable route prefix.

| Method | Route | Name | Description |
|--------|-------|------|-------------|
| GET | `/sso/redirect` | `sso.redirect` | Redirect to tenant's SSO provider |
| GET | `/sso/callback` | `sso.callback` | Handle SSO callback |
| GET | `/neev/sso/redirect` | `sso.redirect` | Redirect to tenant's SSO provider |
| GET | `/neev/sso/callback` | `sso.callback` | Handle SSO callback |

---

Expand Down
15 changes: 9 additions & 6 deletions routes/neev.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@
Route::get('/update-password/{id}/{hash}', [UserAuthController::class, 'updatePasswordCreate'])
->name('reset.request');

//OAuth
Route::get('/oauth/{service}', [OAuthController::class, 'redirect'])
->name('oauth.redirect');
Route::get('/oauth/{service}/callback', [OAuthController::class, 'callback'])
->name('oauth.callback');
//OAuth (web) — under the machine-facing route prefix; these URLs are
// registered as redirect URIs at the identity providers.
Route::prefix(config('neev.route_prefix', 'neev'))->group(function () {
Route::get('/oauth/{service}', [OAuthController::class, 'redirect'])
->name('oauth.redirect');
Route::get('/oauth/{service}/callback', [OAuthController::class, 'callback'])
->name('oauth.callback');
});

Route::get('/email/verify/{id}/{hash}', [UserAuthController::class, 'emailVerifyStore'])
->name('verification.verify');
Expand Down Expand Up @@ -193,7 +196,7 @@
});

//APIs
Route::prefix('/neev')->middleware(TenantMiddleware::class)->group(function () {
Route::prefix(config('neev.route_prefix', 'neev'))->middleware(TenantMiddleware::class)->group(function () {
Route::get('/csrf-cookie', [CsrfCookieController::class, 'show'])
->middleware('throttle:60,1')
->name('neev.csrf-cookie');
Expand Down
Loading