Skip to content

Commit a098974

Browse files
feat(api): add capabilities.login gated by CRE_ENABLE_LOGIN
Expose login alongside myopencre on GET /api/capabilities so auth UI (#943) can roll out behind an env flag; defaults remain off on prod. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f3e638e commit a098974

7 files changed

Lines changed: 62 additions & 7 deletions

File tree

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ INSECURE_REQUESTS=false
3131

3232
CRE_ENABLE_HEALTH=false
3333

34+
# Expose MyOpenCRE in the UI (GET /api/capabilities → myopencre).
35+
# Off by default so production can roll out independently of import auth.
36+
37+
CRE_ENABLE_MYOPENCRE=false
38+
39+
# Expose Login/Logout in the UI (GET /api/capabilities → login).
40+
# Off by default until auth routes + User model land; enable on staging first.
41+
42+
CRE_ENABLE_LOGIN=false
43+
3444
# Embeddings
3545

3646
NO_GEN_EMBEDDINGS=false

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ Then edit `.env` and provide values appropriate for your environment.
289289
* Google Auth: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_SECRET_JSON`, `LOGIN_ALLOWED_DOMAINS`
290290
* GCP: `GCP_NATIVE`
291291
* Spreadsheet Auth: `OpenCRE_gspread_Auth`
292-
* Feature flags: `CRE_ENABLE_HEALTH` (enable the `GET /rest/v1/health` deploy/uptime probe; off by default, returns 404 when unset)
292+
* Feature flags: `CRE_ENABLE_HEALTH` (enable the `GET /rest/v1/health` deploy/uptime probe; off by default, returns 404 when unset), `CRE_ENABLE_MYOPENCRE` (expose MyOpenCRE in `GET /api/capabilities`; off by default), `CRE_ENABLE_LOGIN` (expose Login/Logout UI via `capabilities.login`; off by default)
293293

294294
See `.env.example` for full list and defaults.
295295

application/feature_flags.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ def is_health_endpoint_enabled() -> bool:
2121
def is_myopencre_enabled() -> bool:
2222
"""Return True when the MyOpenCRE feature is enabled via CRE_ENABLE_MYOPENCRE."""
2323
return os.getenv("CRE_ENABLE_MYOPENCRE", "").strip().lower() in TRUE_VALUES
24+
25+
26+
def is_login_enabled() -> bool:
27+
"""Return True when auth UI / login is enabled via CRE_ENABLE_LOGIN."""
28+
return os.getenv("CRE_ENABLE_LOGIN", "").strip().lower() in TRUE_VALUES

application/frontend/src/hooks/useCapabilities.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { useEnvironment } from './useEnvironment';
44

55
export type Capabilities = {
66
myopencre: boolean;
7+
login: boolean;
8+
};
9+
10+
const DEFAULT_CAPABILITIES: Capabilities = {
11+
myopencre: false,
12+
login: false,
713
};
814

915
export const useCapabilities = () => {
@@ -16,8 +22,13 @@ export const useCapabilities = () => {
1622

1723
fetch(`${baseUrl}/api/capabilities`)
1824
.then((res) => res.json())
19-
.then(setCapabilities)
20-
.catch(() => setCapabilities({ myopencre: false }))
25+
.then((data: Partial<Capabilities>) =>
26+
setCapabilities({
27+
myopencre: Boolean(data?.myopencre),
28+
login: Boolean(data?.login),
29+
})
30+
)
31+
.catch(() => setCapabilities(DEFAULT_CAPABILITIES))
2132
.finally(() => setLoading(false));
2233
}, [apiUrl]);
2334

application/frontend/src/routes.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface IRoute {
3737
}
3838
export interface Capabilities {
3939
myopencre: boolean;
40+
login: boolean;
4041
}
4142
export const ROUTES = (capabilities: Capabilities): IRoute[] => [
4243
...(capabilities.myopencre

application/tests/web_main_test.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,16 +1526,19 @@ def test_health_db_unreachable_returns_503(self) -> None:
15261526
finally:
15271527
os.environ.pop("CRE_ENABLE_HEALTH", None)
15281528

1529-
def test_capabilities_endpoint_returns_myopencre_key(self) -> None:
1530-
"""GET /api/capabilities returns 200 with a boolean myopencre key."""
1529+
def test_capabilities_endpoint_returns_capability_keys(self) -> None:
1530+
"""GET /api/capabilities returns 200 with boolean myopencre and login keys."""
15311531
with patch.dict(os.environ, {}, clear=False):
15321532
os.environ.pop("CRE_ENABLE_MYOPENCRE", None)
1533+
os.environ.pop("CRE_ENABLE_LOGIN", None)
15331534
with self.app.test_client() as client:
15341535
response = client.get("/api/capabilities")
15351536
self.assertEqual(200, response.status_code)
15361537
body = json.loads(response.data.decode())
15371538
self.assertIn("myopencre", body)
1539+
self.assertIn("login", body)
15381540
self.assertIsInstance(body["myopencre"], bool)
1541+
self.assertIsInstance(body["login"], bool)
15391542

15401543
def test_capabilities_myopencre_false_by_default(self) -> None:
15411544
"""myopencre is False when CRE_ENABLE_MYOPENCRE is unset."""
@@ -1555,3 +1558,22 @@ def test_capabilities_myopencre_true_when_flag_set(self) -> None:
15551558
self.assertEqual(200, response.status_code)
15561559
body = json.loads(response.data.decode())
15571560
self.assertTrue(body["myopencre"])
1561+
1562+
def test_capabilities_login_false_by_default(self) -> None:
1563+
"""login is False when CRE_ENABLE_LOGIN is unset."""
1564+
with patch.dict(os.environ, {}, clear=False):
1565+
os.environ.pop("CRE_ENABLE_LOGIN", None)
1566+
with self.app.test_client() as client:
1567+
response = client.get("/api/capabilities")
1568+
self.assertEqual(200, response.status_code)
1569+
body = json.loads(response.data.decode())
1570+
self.assertFalse(body["login"])
1571+
1572+
def test_capabilities_login_true_when_flag_set(self) -> None:
1573+
"""login is True when CRE_ENABLE_LOGIN is set to a truthy value."""
1574+
with patch.dict(os.environ, {"CRE_ENABLE_LOGIN": "1"}, clear=False):
1575+
with self.app.test_client() as client:
1576+
response = client.get("/api/capabilities")
1577+
self.assertEqual(200, response.status_code)
1578+
body = json.loads(response.data.decode())
1579+
self.assertTrue(body["login"])

application/web/web_main.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from application.feature_flags import (
2626
is_cre_import_allowed,
2727
is_health_endpoint_enabled,
28+
is_login_enabled,
2829
is_myopencre_enabled,
2930
)
3031

@@ -1202,8 +1203,13 @@ def get_config() -> Any:
12021203

12031204
@app.route("/api/capabilities", methods=["GET"])
12041205
def get_capabilities() -> Any:
1205-
"""Expose frontend feature capabilities, e.g. whether MyOpenCRE is enabled."""
1206-
return jsonify({"myopencre": is_myopencre_enabled()})
1206+
"""Expose frontend feature capabilities (MyOpenCRE, login UI)."""
1207+
return jsonify(
1208+
{
1209+
"myopencre": is_myopencre_enabled(),
1210+
"login": is_login_enabled(),
1211+
}
1212+
)
12071213

12081214

12091215
@app.route("/admin/imports/rerun", methods=["POST"])

0 commit comments

Comments
 (0)