Skip to content

Commit 694a0d1

Browse files
authored
Merge pull request #599 from lbedner/auth-fixes
Auth Fixes
2 parents 66e5c79 + 120b2bf commit 694a0d1

8 files changed

Lines changed: 98 additions & 8 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Each generated project includes:
3232

3333
## Installation
3434

35-
**Current Version**: 0.6.8rc1
35+
**Current Version**: 0.6.8rc2
3636

3737
```bash
3838
pip install aegis-stack

aegis/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Aegis Stack CLI - Component generation and project management tools.
33
"""
44

5-
__version__ = "0.6.8rc1"
5+
__version__ = "0.6.8rc2"

aegis/commands/add_service.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@
1414
validate_copier_project,
1515
validate_git_repository,
1616
)
17-
from ..constants import AnswerKeys, ComponentNames, Messages, StorageBackends
17+
from ..constants import (
18+
AnswerKeys,
19+
AuthLevels,
20+
ComponentNames,
21+
Messages,
22+
StorageBackends,
23+
)
24+
from ..core.auth_service_parser import (
25+
is_auth_service_with_options,
26+
parse_auth_service_config,
27+
)
1828
from ..core.component_utils import (
1929
extract_base_component_name,
2030
extract_base_service_name,
@@ -26,6 +36,7 @@
2636
MIGRATION_SPECS,
2737
bootstrap_alembic,
2838
generate_migration,
39+
get_services_needing_migrations,
2940
service_has_migration,
3041
)
3142
from ..core.project_map import render_project_map
@@ -162,6 +173,24 @@ def add_service_command(
162173
base_service = service_base_map[service]
163174
include_key = AnswerKeys.include_key(base_service)
164175
if existing_answers.get(include_key) is True:
176+
# Special case: auth level upgrades (basic → rbac → org)
177+
if base_service == AnswerKeys.SERVICE_AUTH and is_auth_service_with_options(
178+
service
179+
):
180+
auth_config = parse_auth_service_config(service)
181+
current_level = existing_answers.get(
182+
AnswerKeys.AUTH_LEVEL, AuthLevels.BASIC
183+
)
184+
level_order = {
185+
AuthLevels.BASIC: 0,
186+
AuthLevels.RBAC: 1,
187+
AuthLevels.ORG: 2,
188+
}
189+
if level_order.get(auth_config.level, 0) > level_order.get(
190+
current_level, 0
191+
):
192+
# Upgrading auth level — don't skip
193+
continue
165194
already_enabled.append(service)
166195

167196
if already_enabled:
@@ -362,6 +391,18 @@ def add_service_command(
362391
# Get base service name (strips variant syntax like [langchain,sqlite])
363392
base_service = service_base_map[service]
364393

394+
# For auth service, pass auth level data
395+
if base_service == AnswerKeys.SERVICE_AUTH and is_auth_service_with_options(
396+
service
397+
):
398+
auth_config = parse_auth_service_config(service)
399+
service_data[AnswerKeys.AUTH_LEVEL] = auth_config.level
400+
service_data[AnswerKeys.AUTH_RBAC] = auth_config.level in (
401+
AuthLevels.RBAC,
402+
AuthLevels.ORG,
403+
)
404+
service_data[AnswerKeys.AUTH_ORG] = auth_config.level == AuthLevels.ORG
405+
365406
# For AI service, use the captured configuration
366407
if base_service == AnswerKeys.SERVICE_AI:
367408
# Use providers from interactive config, or default to openai
@@ -441,6 +482,21 @@ def add_service_command(
441482
fg="green",
442483
)
443484

485+
# For auth level upgrades, generate any missing level-specific migrations
486+
# (e.g., auth_rbac, auth_org, auth_tokens)
487+
updated_answers = updater.answers
488+
needed_migrations = get_services_needing_migrations(updated_answers)
489+
for migration_service in needed_migrations:
490+
if migration_service in MIGRATION_SPECS and not service_has_migration(
491+
target_path, migration_service
492+
):
493+
migration_path = generate_migration(target_path, migration_service)
494+
if migration_path:
495+
typer.secho(
496+
f" {t('add_service.generated_migration', name=migration_path.name)}",
497+
fg="green",
498+
)
499+
444500
# Auto-run migrations for services that need them
445501
# Exclude AI service with memory backend (doesn't need migrations)
446502
ai_needs_migrations = (

aegis/core/manual_updater.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@
3838
"app/components/backend/api/deps.py",
3939
}
4040

41+
# Files with Jinja conditionals that depend on auth level (basic/rbac/org).
42+
# Must be regenerated when upgrading auth level.
43+
REGENERATE_ON_AUTH_LEVEL_CHANGE = {
44+
"app/models/user.py",
45+
"app/core/security.py",
46+
"app/services/auth/auth_service.py",
47+
"app/components/backend/api/auth/router.py",
48+
"app/components/backend/api/deps.py",
49+
"app/components/frontend/dashboard/modals/auth_modal.py",
50+
"app/components/frontend/dashboard/modals/auth_users_tab.py",
51+
}
52+
4153

4254
class UpdateResult(BaseModel):
4355
"""Result of a component update operation."""
@@ -142,7 +154,14 @@ def add_component(
142154
# Check if already enabled
143155
include_key = AnswerKeys.include_key(component)
144156
if self.answers.get(include_key) is True:
145-
raise ValueError(f"Component '{component}' is already enabled")
157+
# Allow auth level upgrades (basic → rbac → org)
158+
is_auth_upgrade = (
159+
component == AnswerKeys.SERVICE_AUTH
160+
and additional_data
161+
and AnswerKeys.AUTH_LEVEL in additional_data
162+
)
163+
if not is_auth_upgrade:
164+
raise ValueError(f"Component '{component}' is already enabled")
146165

147166
# Merge additional data
148167
update_data = additional_data or {}
@@ -204,7 +223,15 @@ def add_component(
204223
# Check for conflicts
205224
if output_path.exists():
206225
# Some files have conditional content and must be regenerated
207-
if relative_path in REGENERATE_ON_COMPONENT_CHANGE:
226+
is_auth_upgrade = (
227+
additional_data
228+
and AnswerKeys.AUTH_LEVEL in additional_data
229+
and relative_path in REGENERATE_ON_AUTH_LEVEL_CHANGE
230+
)
231+
if (
232+
relative_path in REGENERATE_ON_COMPONENT_CHANGE
233+
or is_auth_upgrade
234+
):
208235
output_path.write_text(content)
209236
verbose_print(f" Regenerated: {relative_path}")
210237
files_modified.append(relative_path)

aegis/core/post_gen_tasks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ def get_component_file_mapping() -> dict[str, list[str]]:
129129
# Frontend dashboard files
130130
"app/components/frontend/dashboard/cards/auth_card.py",
131131
"app/components/frontend/dashboard/modals/auth_modal.py",
132+
"app/components/frontend/dashboard/modals/auth_users_tab.py",
133+
# Org-level files (cleaned up by post_gen if org not selected)
134+
"app/models/org.py",
135+
"app/components/backend/api/orgs",
136+
"app/components/frontend/dashboard/modals/auth_orgs_tab.py",
137+
"tests/services/test_org_integration.py",
138+
"tests/api/test_org_endpoints.py",
132139
],
133140
AnswerKeys.SERVICE_AI: [
134141
"app/components/backend/api/ai",

copier.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# - Update support
77

88
_min_copier_version: "9.0.0"
9-
_version: "0.6.8rc1"
9+
_version: "0.6.8rc2"
1010

1111
# IMPORTANT: Template content is in subdirectory
1212
# This allows the template to be recognized as git-tracked (aegis-stack repo root has .git)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "aegis-stack"
3-
version = "0.6.8rc1"
3+
version = "0.6.8rc2"
44
description = "A production-ready FastAPI platform with modular components and a built-in control plane. Try: uvx aegis-stack init my-project"
55
readme = "README.md"
66
requires-python = ">=3.11,<3.15"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)