Skip to content

Commit f533f42

Browse files
feat: add auth settings and canonical env overrides
Adds canonical OS_* settings env overrides, Auth settings for registration and Google sign-in, runtime better-auth binding, and updated docs/tests.
1 parent 7240aa5 commit f533f42

35 files changed

Lines changed: 992 additions & 159 deletions

.changeset/silver-ai-envs.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@objectstack/service-settings": major
3+
"@objectstack/plugin-auth": minor
4+
"@objectstack/service-ai": patch
5+
"@objectstack/spec": patch
6+
---
7+
8+
Settings namespace environment overrides now use the canonical ObjectStack
9+
`OS_<NAMESPACE>_<KEY>` form, with no unprefixed aliases. For example,
10+
`ai.openai_base_url` is now `OS_AI_OPENAI_BASE_URL`, and
11+
`feature_flags.ai_enabled` is now `OS_FEATURE_FLAGS_AI_ENABLED`.
12+
13+
The AI service now treats a stored or env-locked `provider=memory` setting as
14+
an explicit override, while the manifest default still leaves boot-time
15+
provider auto-detection intact.
16+
17+
The auth plugin now binds the `auth` settings namespace to better-auth runtime
18+
configuration, exposes an extension hook for provider packages, and includes a
19+
basic Google sign-in implementation configured either in Setup → Authentication
20+
or by deployment-level `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`.

content/docs/guides/auth-sso.mdx

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
---
22
title: Social & Enterprise SSO
3-
description: Enable social sign-in (Google, GitHub, Microsoft, Apple) and enterprise OIDC/SSO in ObjectStack Studio.
3+
description: Enable Google sign-in in open source, and extend auth providers through packages.
44
---
55

66
# Social & Enterprise SSO
77

8-
ObjectStack supports multiple authentication providers out of the box:
8+
ObjectStack open source ships one built-in social sign-in implementation:
99

10-
- **Social OAuth**: Google, GitHub, Microsoft, Apple — via better-auth's built-in `socialProviders`
11-
- **Enterprise SSO**: Any OIDC-compliant provider (Okta, Azure AD, Keycloak, Ping Identity, …) — via better-auth's `genericOAuth` plugin
10+
- **Google OAuth**: configure in Setup → Authentication, or from
11+
`GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` deployment env vars
1212

13-
The Studio login and registration pages automatically render **Continue with …** buttons for every enabled provider. No UI changes are required.
13+
Additional providers should be contributed by product or enterprise packages through
14+
the `auth:configure` hook. Those packages can add better-auth `socialProviders`
15+
or OIDC/generic OAuth providers without forking `@objectstack/plugin-auth`.
16+
17+
The Studio login and registration pages automatically render **Continue with ...**
18+
buttons for every enabled provider returned by `/api/v1/auth/config`.
1419

1520
---
1621

@@ -27,15 +32,24 @@ The Studio login and registration pages automatically render **Continue with …
2732
For local development: `http://localhost:3000/api/v1/auth/callback/google`
2833
4. Copy the **Client ID** and **Client Secret**.
2934

35+
Then either save the values in **Setup → Authentication → Social sign-in** or
36+
provide them as deployment environment variables:
37+
3038
```bash
3139
GOOGLE_CLIENT_ID=your-google-client-id
3240
GOOGLE_CLIENT_SECRET=your-google-client-secret
3341

34-
# Optional: override default scopes (comma-separated)
35-
# GOOGLE_OAUTH_SCOPES=openid,email,profile
42+
# Optional settings env override. UI settings are lower precedence than env.
43+
# OS_AUTH_GOOGLE_ENABLED=false
3644
```
3745

38-
### GitHub
46+
## Extension Packages
47+
48+
The providers below are examples for enterprise or product packages, not built-in
49+
open-source settings. A package can register them by listening to `auth:configure`
50+
and mutating the draft auth config.
51+
52+
### GitHub Example
3953

4054
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)**OAuth Apps****New OAuth App**.
4155
2. Set **Authorization callback URL**:
@@ -84,14 +98,16 @@ APPLE_CLIENT_SECRET=your-apple-private-key-jwt
8498
8599
---
86100

87-
## Enterprise SSO (OIDC)
101+
## Enterprise SSO (OIDC) Extension Example
88102

89-
For enterprise single sign-on, set the `OIDC_PROVIDERS` environment variable to a JSON array. Each entry maps to one SSO provider.
103+
Enterprise packages can pass `oidcProviders` into `@objectstack/plugin-auth`
104+
or contribute them through `auth:configure`. The open-source package does not
105+
ship a generic OIDC settings UI.
90106

91107
### Quick start — Okta
92108

93-
```bash
94-
OIDC_PROVIDERS='[
109+
```ts
110+
const oidcProviders = [
95111
{
96112
"providerId": "okta",
97113
"name": "Okta",
@@ -100,27 +116,27 @@ OIDC_PROVIDERS='[
100116
"clientSecret": "your-okta-client-secret",
101117
"scopes": ["openid", "email", "profile"]
102118
}
103-
]'
119+
];
104120
```
105121

106122
### Quick start — Azure AD (OIDC)
107123

108-
```bash
109-
OIDC_PROVIDERS='[
124+
```ts
125+
const oidcProviders = [
110126
{
111127
"providerId": "azure-ad",
112128
"name": "Azure AD",
113129
"discoveryUrl": "https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration",
114130
"clientId": "your-azure-client-id",
115131
"clientSecret": "your-azure-client-secret"
116132
}
117-
]'
133+
];
118134
```
119135

120136
### Quick start — Keycloak
121137

122-
```bash
123-
OIDC_PROVIDERS='[
138+
```ts
139+
const oidcProviders = [
124140
{
125141
"providerId": "keycloak",
126142
"name": "Keycloak",
@@ -129,13 +145,13 @@ OIDC_PROVIDERS='[
129145
"clientSecret": "your-client-secret",
130146
"pkce": true
131147
}
132-
]'
148+
];
133149
```
134150

135151
### Multiple enterprise providers
136152

137-
```bash
138-
OIDC_PROVIDERS='[
153+
```ts
154+
const oidcProviders = [
139155
{
140156
"providerId": "okta",
141157
"name": "Okta SSO",
@@ -150,7 +166,7 @@ OIDC_PROVIDERS='[
150166
"clientId": "...",
151167
"clientSecret": "..."
152168
}
153-
]'
169+
];
154170
```
155171

156172
### OIDC provider fields

content/docs/guides/authentication.mdx

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ The `@objectstack/plugin-auth` package provides enterprise-grade authentication
3333
-**Session Management** - Automatic session handling with configurable expiry
3434
-**Password Reset** - Email-based password reset flow
3535
-**Email Verification** - Email verification workflow
36-
- **2FA** - Two-factor authentication
36+
- ⚙️ **2FA backend** - better-auth two-factor plugin wiring is available for custom UIs
3737
-**Passkeys** - WebAuthn/Passkey support
3838
-**Magic Links** - Passwordless authentication
3939
-**Organizations** - Multi-tenant support
@@ -91,14 +91,19 @@ Set up your authentication secret in `.env`:
9191

9292
```bash
9393
# Required: Secret for session token encryption
94-
AUTH_SECRET=your-super-secret-key-min-32-chars
94+
OS_AUTH_SECRET=your-super-secret-key-min-32-chars
9595

96-
# Optional: OAuth providers (if using)
96+
# Optional: open-source Google login
97+
# You can also configure these in Setup → Authentication.
9798
GOOGLE_CLIENT_ID=your-google-client-id
9899
GOOGLE_CLIENT_SECRET=your-google-client-secret
100+
101+
# Optional: lock auth settings from env. Env wins over Setup UI values.
102+
OS_AUTH_SIGNUP_ENABLED=false
103+
OS_AUTH_GOOGLE_ENABLED=true
99104
```
100105

101-
> **Important**: Never commit `AUTH_SECRET` to version control. Use a strong random string (minimum 32 characters).
106+
> **Important**: Never commit `OS_AUTH_SECRET` to version control. Use a strong random string (minimum 32 characters).
102107
103108
### 2. Plugin Configuration
104109

@@ -255,7 +260,7 @@ Enable OAuth providers in your plugin configuration:
255260

256261
```typescript
257262
new AuthPlugin({
258-
secret: process.env.AUTH_SECRET,
263+
secret: process.env.OS_AUTH_SECRET,
259264
baseUrl: 'http://localhost:3000',
260265
providers: [
261266
{
@@ -291,14 +296,23 @@ Better-Auth automatically handles the OAuth callback at `/api/v1/auth/callback/g
291296

292297
### Two-Factor Authentication (2FA)
293298

294-
Enable 2FA in your configuration:
299+
ObjectStack wires the better-auth two-factor plugin at the backend layer, but
300+
the open-source Console login UI does not yet ship the complete TOTP challenge
301+
flow. Do not expose 2FA as an end-user feature until your UI handles:
302+
303+
- enrollment (`/two-factor/enable`),
304+
- TOTP confirmation (`/two-factor/verify-totp`),
305+
- the `twoFactorRedirect` response returned by password sign-in, and
306+
- backup-code recovery.
307+
308+
For custom account UIs, enable the backend plugin in configuration:
295309

296310
```typescript
297311
new AuthPlugin({
298312
secret: process.env.AUTH_SECRET,
299313
baseUrl: 'http://localhost:3000',
300314
plugins: {
301-
twoFactor: true, // Enable 2FA
315+
twoFactor: true, // Enable backend two-factor endpoints
302316
}
303317
})
304318
```
@@ -311,17 +325,20 @@ const response = await fetch('http://localhost:3000/api/v1/auth/two-factor/enabl
311325
headers: {
312326
'Content-Type': 'application/json',
313327
'Authorization': `Bearer ${accessToken}`
314-
}
328+
},
329+
body: JSON.stringify({
330+
password: currentPassword,
331+
}),
315332
});
316333

317-
const { qrCode, secret } = await response.json();
318-
// Display qrCode to user for scanning with authenticator app
334+
const { totpURI, backupCodes } = await response.json();
335+
// Display totpURI as a QR code and show backupCodes once.
319336
```
320337

321338
#### Verify 2FA Code
322339

323340
```typescript
324-
const response = await fetch('http://localhost:3000/api/v1/auth/two-factor/verify', {
341+
const response = await fetch('http://localhost:3000/api/v1/auth/two-factor/verify-totp', {
325342
method: 'POST',
326343
headers: { 'Content-Type': 'application/json' },
327344
body: JSON.stringify({
@@ -515,8 +532,8 @@ All endpoints are available under `/api/v1/auth/*`:
515532

516533
#### Two-Factor Authentication
517534

518-
- `POST /api/v1/auth/two-factor/enable` - Enable 2FA for user
519-
- `POST /api/v1/auth/two-factor/verify` - Verify 2FA code
535+
- `POST /api/v1/auth/two-factor/enable` - Start 2FA enrollment for the current user
536+
- `POST /api/v1/auth/two-factor/verify-totp` - Verify a TOTP code
520537

521538
#### Passkeys
522539

@@ -554,7 +571,7 @@ For complete API documentation, see the [Better-Auth API Reference](https://www.
554571
3. **Environment Variables**: Never commit secrets to version control
555572
```bash
556573
# .env (not committed)
557-
AUTH_SECRET=your-secret-here
574+
OS_AUTH_SECRET=your-secret-here
558575
GOOGLE_CLIENT_ID=your-client-id
559576
```
560577

@@ -574,7 +591,7 @@ For complete API documentation, see the [Better-Auth API Reference](https://www.
574591

575592
1. **Email Verification**: Require email verification for sensitive operations
576593
2. **Password Requirements**: Enforce strong password policies
577-
3. **2FA for Admin**: Require 2FA for administrative accounts
594+
3. **2FA for Admin**: Require 2FA only after your UI supports the full TOTP challenge and recovery flow
578595
4. **OAuth Options**: Provide multiple OAuth providers for convenience
579596

580597
### Error Handling

content/docs/guides/environment-variables.mdx

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,29 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false
6060
|:---|:---|:---|:---|
6161
| `OS_AUTH_URL` | url | `http://localhost:<OS_PORT>` | Public base URL of the auth server. Required behind a proxy or in production. |
6262
| `OS_AUTH_SECRET` | string | auto-generated (dev) | Secret used to sign sessions and cookies. **Required** in production. |
63-
| `OS_AUTH_ADMIN` | boolean | `true` | Enable the admin plugin (first user becomes admin). |
64-
| `OS_AUTH_TWO_FACTOR` | boolean | `true` | Enable the two-factor authentication plugin. |
63+
| `OS_AUTH_TWO_FACTOR` | boolean | `false` | Enable the low-level better-auth two-factor plugin. Keep disabled unless your UI handles enrollment, login challenge, and backup-code recovery. |
6564
| `OS_DISABLE_SIGNUP` | boolean | `false` | When `true`, block new email/password sign-ups. The very first user can still sign up to bootstrap admin. |
65+
| `OS_AUTH_EMAIL_PASSWORD_ENABLED` | boolean | settings default | Settings env override for `auth.email_password_enabled`. Controls local email/password login. |
66+
| `OS_AUTH_SIGNUP_ENABLED` | boolean | settings default | Settings env override for `auth.signup_enabled`. Takes precedence over UI settings and is preferred over `OS_DISABLE_SIGNUP`. |
67+
| `OS_AUTH_REQUIRE_EMAIL_VERIFICATION` | boolean | settings default | Settings env override for `auth.require_email_verification`. |
68+
| `OS_AUTH_GOOGLE_ENABLED` | boolean | settings default | Settings env override for `auth.google_enabled`. Requires Google OAuth credentials from Settings or env. |
69+
| `GOOGLE_CLIENT_ID` | string || Deployment-level Google OAuth client id for the open-source Google login implementation. |
70+
| `GOOGLE_CLIENT_SECRET` | string || Deployment-level Google OAuth client secret for the open-source Google login implementation. |
6671
| `OS_MULTI_ORG_ENABLED` | boolean | `false` | When `true`, expose organization creation/switching UI. When `false`, run in single-tenant mode. |
6772
| `OS_OIDC_PROVIDER_ENABLED` | boolean | `false` | When `true`, expose this instance as an OIDC identity provider. |
6873
| `OS_COOKIE_DOMAIN` | string || Cookie domain for cross-subdomain session sharing (e.g. `.example.com`). |
6974
| `OS_TRUSTED_ORIGINS` | csv || Comma-separated list of origins permitted for auth callbacks / CSRF. |
7075
| `OS_TERMS_URL` | url || URL shown next to the "Terms of Service" link on the sign-up form. Empty string hides the link. |
7176
| `OS_PRIVACY_URL` | url || URL shown next to the "Privacy Policy" link on the sign-up form. Empty string hides the link. |
7277

78+
Auth settings precedence:
79+
80+
1. Environment variables win over values saved in the Setup UI.
81+
2. `OS_AUTH_<SETTING_KEY>` settings env vars are the canonical form for settings-backed auth controls.
82+
3. Existing deployment toggles such as `OS_AUTH_TWO_FACTOR` and `OS_DISABLE_SIGNUP` remain supported.
83+
4. Google credentials can be saved in Setup → Authentication. `google_client_secret` is encrypted at rest.
84+
5. `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` remain supported for deployment-level Google OAuth configuration.
85+
7386
---
7487

7588
## CORS
@@ -112,11 +125,69 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false
112125

113126
| Variable | Type | Default | Description |
114127
|:---|:---|:---|:---|
115-
| `OS_AI_MODEL` | string | `gpt-4o-mini` | Default chat model id. |
116-
| `OS_AI_ACTION_API_BASE_URL` | url || Override the base URL the AI action runtime calls (useful for self-hosted proxies). |
117-
118-
> Provider credentials (`OPENAI_API_KEY`, `AI_GATEWAY_*`, etc.) keep their
119-
> upstream names.
128+
| `AI_GATEWAY_MODEL` | string || Vercel AI Gateway model id (for example `openai/gpt-4.1-mini` or `anthropic/claude-sonnet-4-5`). When set, the AI service boots with the gateway adapter before trying direct providers. |
129+
| `AI_GATEWAY_API_KEY` | string || Vercel AI Gateway API key. Required by the gateway SDK for real gateway calls; by itself it does not select the gateway adapter. Pair it with `AI_GATEWAY_MODEL`. |
130+
| `OPENAI_API_KEY` | string || Selects the direct OpenAI adapter when no gateway model is configured. Default model: `gpt-4o`. |
131+
| `ANTHROPIC_API_KEY` | string || Selects the direct Anthropic adapter when gateway and OpenAI are not configured. Default model: `claude-sonnet-4-20250514`. |
132+
| `GOOGLE_GENERATIVE_AI_API_KEY` | string || Selects the direct Google Gemini adapter when gateway, OpenAI, and Anthropic are not configured. Default model: `gemini-2.0-flash`. |
133+
| `OS_AI_MODEL` | string | provider default | Overrides the model id for direct OpenAI / Anthropic / Google provider detection. Does not override `AI_GATEWAY_MODEL`; gateway model ids already include the provider prefix. |
134+
| `OS_AI_ACTION_API_BASE_URL` | url || Base URL prepended to relative `type: 'api'` action targets exposed as AI tools. Without this or a plugin `apiActionBaseUrl`, relative API actions are skipped for AI exposure. |
135+
| `OS_AI_PROVIDER` | enum || Settings override for the AI provider. Supported values include `gateway`, `openai`, `anthropic`, `google`, `deepseek`, `dashscope`, `cloudflare`, `siliconflow`, and `openrouter`. When set, it locks the Settings UI value. |
136+
| `OS_AI_GATEWAY_MODEL` | string || Settings override for the Vercel AI Gateway model id. Use with `OS_AI_PROVIDER=gateway`. |
137+
| `OS_AI_GATEWAY_API_KEY` | string || Settings override for the Vercel AI Gateway API key. Use with `OS_AI_PROVIDER=gateway`. |
138+
| `OS_AI_OPENAI_BASE_URL` | url || Settings override for the OpenAI-compatible chat base URL. Use with `OS_AI_PROVIDER=openai` for Azure, local gateways, or third-party OpenAI-shaped APIs. |
139+
| `OS_AI_OPENAI_API_KEY` | string || Settings override for the OpenAI-compatible API key. Stored only in env; the Settings UI cannot overwrite it while present. |
140+
| `OS_AI_OPENAI_MODEL` | string | `gpt-4o` | Settings override for the OpenAI-compatible chat model id. |
141+
| `OS_AI_ANTHROPIC_API_KEY` | string || Settings override for the Anthropic API key. Use with `OS_AI_PROVIDER=anthropic`. |
142+
| `OS_AI_ANTHROPIC_MODEL` | string | `claude-sonnet-4-20250514` | Settings override for the Anthropic model id. |
143+
| `OS_AI_GOOGLE_API_KEY` | string || Settings override for the Google Generative AI API key. Use with `OS_AI_PROVIDER=google`. |
144+
| `OS_AI_GOOGLE_MODEL` | string | `gemini-2.0-flash` | Settings override for the Google model id. |
145+
| `OS_AI_<PROVIDER>_API_KEY` | string || Settings override for preset OpenAI-compatible providers, for example `OS_AI_DEEPSEEK_API_KEY`, `OS_AI_DASHSCOPE_API_KEY`, `OS_AI_SILICONFLOW_API_KEY`, or `OS_AI_OPENROUTER_API_KEY`. |
146+
| `OS_AI_<PROVIDER>_MODEL` | string | provider default | Settings override for preset provider model ids, for example `OS_AI_DEEPSEEK_MODEL=deepseek-chat`. |
147+
148+
The boot-time LLM adapter priority is:
149+
150+
1. Explicit `AIServicePlugin({ adapter })` or per-plugin `gatewayModel` option.
151+
2. `AI_GATEWAY_MODEL` using `@ai-sdk/gateway`.
152+
3. `OPENAI_API_KEY` using `@ai-sdk/openai`.
153+
4. `ANTHROPIC_API_KEY` using `@ai-sdk/anthropic`.
154+
5. `GOOGLE_GENERATIVE_AI_API_KEY` using `@ai-sdk/google`.
155+
6. `MemoryLLMAdapter` echo mode when no real provider is configured.
156+
157+
After boot, if the Settings service is mounted, `@objectstack/service-ai`
158+
reads the `ai` Settings namespace and swaps the adapter when the provider
159+
comes from env or a stored Settings UI value. The manifest default
160+
`provider=memory` is ignored so it does not mask boot-time auto-detection;
161+
an explicit `OS_AI_PROVIDER=memory` or stored UI value does switch the runtime
162+
back to memory. Settings values resolve as:
163+
164+
```text
165+
env (`OS_<NAMESPACE>_<KEY>`) > stored Settings UI value > default
166+
```
167+
168+
That means `OS_AI_PROVIDER=openai` plus `OS_AI_OPENAI_BASE_URL=...` wins over
169+
both the Settings UI and the boot-time provider auto-detection. ObjectStack
170+
does not read unprefixed settings override names such as `AI_PROVIDER` or
171+
`AI_OPENAI_BASE_URL`.
172+
173+
For example, a third-party OpenAI-compatible provider can be deployment-locked
174+
without using the Settings UI:
175+
176+
```bash
177+
OS_AI_PROVIDER=openai
178+
OS_AI_OPENAI_API_KEY=sk-...
179+
OS_AI_OPENAI_MODEL=your-model
180+
OS_AI_OPENAI_BASE_URL=https://your-provider.example.com/v1
181+
```
182+
183+
Provider credentials keep their upstream names. ObjectStack does not rename
184+
`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, or
185+
`AI_GATEWAY_*` to `OS_*`.
186+
187+
> **OpenAI-compatible base URLs.** Use `OS_AI_OPENAI_BASE_URL` with
188+
> `OS_AI_PROVIDER=openai`, configure the same fields in **Settings → AI**, or
189+
> pass an explicit adapter in code. Do not rely on `OPENAI_BASE_URL` as a
190+
> platform-level environment variable.
120191
121192
---
122193

0 commit comments

Comments
 (0)