Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
02dd4be
feat: add DynamicSecret and DynamicSecretLease models with related fi…
rohan-chaturvedi Aug 30, 2025
7dbe60d
feat: backend utils, resolvers and types
rohan-chaturvedi Aug 30, 2025
fbb40b3
feat: generate schema and types
rohan-chaturvedi Aug 30, 2025
c9cc5dd
feat: add misc frontend utils
rohan-chaturvedi Aug 30, 2025
8daa7e2
feat: misc improvements to stepper styling
rohan-chaturvedi Aug 30, 2025
428217c
feat: add support for custom classes to input and label
rohan-chaturvedi Aug 30, 2025
79bb77f
fix: textarea label spacing
rohan-chaturvedi Aug 30, 2025
c687a86
fix: ghost button spinner color
rohan-chaturvedi Aug 30, 2025
2a2efb8
feat: add client side queries for providers, secrets and leases
rohan-chaturvedi Aug 30, 2025
62ff986
feat: add client mutations to crud secrets and leases
rohan-chaturvedi Aug 30, 2025
986d2c8
fix: alert spacing
rohan-chaturvedi Aug 30, 2025
b467f8a
fix: aws region picker label
rohan-chaturvedi Aug 30, 2025
2afb906
feat: dialog to create dynamic secrets
rohan-chaturvedi Aug 30, 2025
0da7f7c
feat: frontend to view and manage dynamic secrets and leases
rohan-chaturvedi Aug 30, 2025
9df7915
feat: add dynamic secrets to integrations page
rohan-chaturvedi Aug 30, 2025
133f236
feat: schedule all active leases for revoation when deleting a secret
rohan-chaturvedi Sep 2, 2025
6ea430c
feat: refactor dynamic secret creation, validation, revocation logic
rohan-chaturvedi Sep 2, 2025
16cc210
feat: enhance duplicate check to include dynamic secrets key_map
rohan-chaturvedi Sep 2, 2025
2542040
feat: simplify secret deletion by directly calling delete method
rohan-chaturvedi Sep 2, 2025
c65b5d1
feat: encrypt key names in dynamic secret creation process
rohan-chaturvedi Sep 2, 2025
4a14428
feat: encrypt key names in dynamic secret update process
rohan-chaturvedi Sep 2, 2025
3538c41
feat: improve layout of TTL input and buttons in CreateLeaseDialog
rohan-chaturvedi Sep 2, 2025
1f83fcc
feat: add environment prop to DynamicSecretRow
rohan-chaturvedi Sep 2, 2025
7f19844
feat: enhance DynamicSecret component layout and add link to view secret
rohan-chaturvedi Sep 2, 2025
455a691
feat: add dynamic secrets handling and decryption in EnvironmentPath …
rohan-chaturvedi Sep 2, 2025
e8ba641
feat: add and enforce permissions for DynamicSecretLeases
rohan-chaturvedi Sep 2, 2025
6934885
fix: duplicate check logic for dynamic secret key map
rohan-chaturvedi Sep 2, 2025
ff72d49
fix: cleanup create dialog props and rest form state on create
rohan-chaturvedi Sep 2, 2025
8e1e076
feat: misc updates and improvements to TTL inputs
rohan-chaturvedi Sep 3, 2025
1f08f56
fix: duplicate key checking for existing dynamic secrets
rohan-chaturvedi Sep 3, 2025
fb3cef1
feat: reorganize dynamic secret lease utils and improve error handling
rohan-chaturvedi Sep 4, 2025
c83a2f9
feat: add rest api serializer for leased dynamic secrets
rohan-chaturvedi Sep 4, 2025
b199db6
fix: correctly parse path query var when fetching dynamic secrets
rohan-chaturvedi Sep 4, 2025
dee747c
fix: misc fixes to path handling and other cleanup
rohan-chaturvedi Sep 4, 2025
fd5486c
chore: misc cleanup, formatting
rohan-chaturvedi Sep 4, 2025
bf3262e
fix: dynamic secret row ring color
rohan-chaturvedi Sep 4, 2025
10879b0
fix: delete secret button style
rohan-chaturvedi Sep 4, 2025
b6727f7
fix: permission checks for renew and revoke operations on leases for …
rohan-chaturvedi Sep 4, 2025
c1a8f2a
fix: lease card typography
rohan-chaturvedi Sep 4, 2025
8cf5fa0
fix: correct key name in credentials serializer
rohan-chaturvedi Sep 4, 2025
6b526f6
fix: ensure secret name is unique per env and path
rohan-chaturvedi Sep 5, 2025
cabc0b4
fix: apply search queries to dynamic secrets
rohan-chaturvedi Sep 5, 2025
6daef3d
feat: api
rohan-chaturvedi Sep 7, 2025
089a386
Potential fix for code scanning alert no. 21: Information exposure th…
rohan-chaturvedi Sep 8, 2025
f47f2d9
Merge branch 'main' into feat--dynamic-secrets
rohan-chaturvedi Sep 8, 2025
68d81b2
fix: logger
rohan-chaturvedi Sep 8, 2025
f564bff
feat: misc updates and cleanup
rohan-chaturvedi Sep 8, 2025
584d217
fix: tailwind content path
rohan-chaturvedi Sep 8, 2025
5d09b56
fix: dyanmic secret provider titlte on light theme
nimish-ks Sep 9, 2025
ffb4743
fix: add vertical space between add service credentials button
nimish-ks Sep 9, 2025
4bc01d2
fix: update label for username template in dynamic secret providers
nimish-ks Sep 9, 2025
a56bdcf
Merge branch 'main' into feat--dynamic-secrets
rohan-chaturvedi Sep 10, 2025
51712a9
fix: update dynamic secret row styling to be more consistent with sta…
rohan-chaturvedi Sep 10, 2025
f04383d
feat: add reveal toggle for AWS credentials in dynamic secrets
rohan-chaturvedi Sep 10, 2025
20107aa
feat: log lease events with actors and metadata
rohan-chaturvedi Sep 10, 2025
240689c
chore: regenerate schema and types
rohan-chaturvedi Sep 10, 2025
c330444
feat: render lease event history
rohan-chaturvedi Sep 10, 2025
434147c
feat: misc ux improvements to renew lease dialog
rohan-chaturvedi Sep 10, 2025
2c829d0
fix: clean up manage lease dialog header
rohan-chaturvedi Sep 10, 2025
d84efb0
fix: typo
rohan-chaturvedi Sep 10, 2025
5f39565
feat: replace 'system' placeholder with phase logo wordmark for serve…
rohan-chaturvedi Sep 10, 2025
135dd10
feat: add empty state on integrations screen
rohan-chaturvedi Sep 11, 2025
cbbed78
fix: misc bugfixes
rohan-chaturvedi Sep 11, 2025
56b1b3c
chore: cleanup urls
rohan-chaturvedi Sep 12, 2025
faa4690
chore: update key map field default
rohan-chaturvedi Sep 12, 2025
8cf1e61
feat: extend PhaseTokenAuthentication to infer env from secret_id
rohan-chaturvedi Sep 12, 2025
1606c06
feat: add lease owner to serializer
rohan-chaturvedi Sep 12, 2025
7188569
fix: add support for fetching dynamic secrets via public api without …
rohan-chaturvedi Sep 12, 2025
eb81548
fix: align value placeholders with key
rohan-chaturvedi Sep 12, 2025
2b9d603
fix: make exclude clause conditional for safety
rohan-chaturvedi Sep 15, 2025
d3e5c05
fix: clean up account assignment in dynamic secrets view
rohan-chaturvedi Sep 15, 2025
6afd3b7
feat: update leases fetch policy to cache-and-network
rohan-chaturvedi Sep 15, 2025
c2a2a05
fix: combine static and dynamic secrets into single response list
rohan-chaturvedi Sep 15, 2025
9b37464
fix: update return type for public api
rohan-chaturvedi Sep 15, 2025
fc23798
fix: correctly fetch request meta, mark lease as revoked if user not …
rohan-chaturvedi Sep 15, 2025
8899214
fix: enforce minimum ttl
rohan-chaturvedi Sep 15, 2025
cfdf207
feat: add masked state to provider keymap config
rohan-chaturvedi Sep 15, 2025
8778f3b
fix: toast on lease revoke
rohan-chaturvedi Sep 15, 2025
f2a7196
fix: allow 60 second TTL
rohan-chaturvedi Sep 15, 2025
473d0c2
fix: strip masked field to correctly cast type, fix key name format
rohan-chaturvedi Sep 15, 2025
4fcba95
fix: refetch dynamic secrets on create and delete
rohan-chaturvedi Sep 15, 2025
2a576f6
fix: correctly handle groups config, handle both arns and group names
rohan-chaturvedi Sep 16, 2025
59dbfe4
feat: add detailed logging, cleanup user if lease creation fails
rohan-chaturvedi Sep 16, 2025
2eb7ac6
feat: add log data to create event meta
rohan-chaturvedi Sep 16, 2025
248d764
fix: preselect first available aws cred, update cred picker styling
rohan-chaturvedi Sep 16, 2025
71f6d48
feat: add dynamic secrets to cross env screen
rohan-chaturvedi Sep 16, 2025
6d7834d
feat: add active lease warning on delete dialog
rohan-chaturvedi Sep 16, 2025
ada85ee
feat: misc ui cleanup
rohan-chaturvedi Sep 16, 2025
550f85d
chore: misc code cleanup
rohan-chaturvedi Sep 16, 2025
1ac14bd
feat: update renewal behavior to extend total ttl, update ui
rohan-chaturvedi Sep 17, 2025
5334eba
feat: misc ui fixes and improvements
rohan-chaturvedi Sep 17, 2025
44b4733
feat: move verbose timestamps to title attrs
rohan-chaturvedi Sep 17, 2025
7468b6d
fix: misc ui cleanup for secret menus
rohan-chaturvedi Sep 17, 2025
6611bb4
feat: display used, remaining and total ttl in human readable format
rohan-chaturvedi Sep 18, 2025
5ed9483
fix: clamp progress bar to 100%
rohan-chaturvedi Sep 18, 2025
0d3ff8e
fix: pass external id when assuming role via sts
nimish-ks Sep 25, 2025
d376038
feat: unambigious sts client construction
nimish-ks Sep 25, 2025
6c721f9
fix: replace concat with extend for dynamic secrets in response data
nimish-ks Sep 26, 2025
f55c5f0
fix: correctly log org member during revoke via rest
rohan-chaturvedi Sep 27, 2025
b9f2e37
feat: implement custom exceptions for dynamic secret operations and l…
rohan-chaturvedi Sep 27, 2025
6ea662e
chore: clean up types
rohan-chaturvedi Sep 27, 2025
a4067ec
fix: ensure current TTL is safely accessed in RenewLeaseDialog
rohan-chaturvedi Sep 27, 2025
825a50e
fix: add dynamic secrets to secret count for folder type
rohan-chaturvedi Sep 27, 2025
0857852
feat: add copy buttons for secret and lease id
rohan-chaturvedi Sep 27, 2025
25d615c
fix: add loading state to leases list
rohan-chaturvedi Sep 28, 2025
a1954c4
feat: improve configure secret dialog header
rohan-chaturvedi Sep 28, 2025
2e6f786
fix: return 404 if no dynamic secrets match filter
rohan-chaturvedi Sep 28, 2025
b7fc04a
fix: clean up duplicate code
rohan-chaturvedi Sep 28, 2025
0dd6e53
fix: correctly apply lease filter, misc cleanup
rohan-chaturvedi Sep 28, 2025
f49512a
feat: add ttl param, granular exception handling to dynamic secrets view
rohan-chaturvedi Sep 28, 2025
76e44e2
chore: cleanup
rohan-chaturvedi Sep 28, 2025
10328dd
fix: change exception type from AuthenticationFailed to NotFound for …
rohan-chaturvedi Sep 28, 2025
e187822
feat: add optional lease_ttl param to control ttl for leased credentials
rohan-chaturvedi Sep 28, 2025
bcda6d1
feat: return an error response for combined secrets api if leases can…
rohan-chaturvedi Sep 29, 2025
13a3a62
feat: enhance lease status display by indicating leases that due to e…
rohan-chaturvedi Sep 30, 2025
1f9e6be
feat: improve spacing and layout in create and update dialogs
rohan-chaturvedi Sep 30, 2025
a89ef7e
chore: bump version
rohan-chaturvedi Sep 30, 2025
d484443
fix: prevent bad ttl values being passed to mutation vars in case for…
rohan-chaturvedi Sep 30, 2025
5178a0f
fix: clean up borders
rohan-chaturvedi Sep 30, 2025
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: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"cSpell.words": ["organisation"] // Don't run prettier for files listed in .gitignore
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
} // Don't run prettier for files listed in .gitignore
}
77 changes: 51 additions & 26 deletions backend/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
get_token_type,
token_is_expired_or_deleted,
)
from api.models import Environment
from api.models import DynamicSecret, Environment, Secret
from api.utils.access.permissions import (
service_account_can_access_environment,
user_can_access_environment,
Expand Down Expand Up @@ -44,34 +44,59 @@ def authenticate(self, request):
if token_is_expired_or_deleted(auth_token):
raise exceptions.AuthenticationFailed("Token expired or deleted")

env_id = request.headers.get("environment")
env = None

# Try resolving env from header
if env_id:
# Try resolving secret_id from header OR query params (supports Secret or DynamicSecret)
secret_id = request.headers.get("secret_id") or request.GET.get("secret_id")
if secret_id:
found = False
try:
env = Environment.objects.get(id=env_id)
except Environment.DoesNotExist:
raise exceptions.AuthenticationFailed("Environment not found")
secret = Secret.objects.get(id=secret_id)
env = secret.environment
found = True
except Secret.DoesNotExist:
pass
if not found:
try:
dyn_secret = DynamicSecret.objects.get(id=secret_id)
env = dyn_secret.environment
found = True
except DynamicSecret.DoesNotExist:
pass
if not found:
raise exceptions.NotFound("Secret not found")

# If env is still None, try resolving from header or query params
if env is None:
env_id = request.headers.get("environment")
# Try resolving env from header
if env_id:
try:
env = Environment.objects.get(id=env_id)
except Environment.DoesNotExist:
raise exceptions.AuthenticationFailed("Environment not found")

# Try resolving env from query params
else:
try:
app_id = request.GET.get("app_id")
env_name = request.GET.get("env")
if not app_id:
raise exceptions.AuthenticationFailed("Missing app_id parameter")
if not env_name:
raise exceptions.AuthenticationFailed("Missing env parameter")
env = Environment.objects.get(app_id=app_id, name__iexact=env_name)
except Environment.DoesNotExist:
# Check if the app exists to give a more specific error
App = apps.get_model("api", "App")
if not App.objects.filter(id=app_id).exists():
raise exceptions.NotFound(f"App with ID {app_id} not found")
else:
raise exceptions.NotFound(
f"Environment '{env_name}' not found in App {app_id}"
)
# Try resolving env from query params
else:
try:
app_id = request.GET.get("app_id")
env_name = request.GET.get("env")
if not app_id:
raise exceptions.AuthenticationFailed(
"Missing app_id parameter"
)
if not env_name:
raise exceptions.AuthenticationFailed("Missing env parameter")
env = Environment.objects.get(app_id=app_id, name__iexact=env_name)
except Environment.DoesNotExist:
# Check if the app exists to give a more specific error
App = apps.get_model("api", "App")
if not App.objects.filter(id=app_id).exists():
raise exceptions.NotFound(f"App with ID {app_id} not found")
else:
raise exceptions.NotFound(
f"Environment '{env_name}' not found in App {app_id}"
)

auth["environment"] = env

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Generated by Django 4.2.22 on 2025-08-20 08:11

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('api', '0105_environmentkey_unique_envkey_user_and_more'),
]

operations = [
migrations.CreateModel(
name='DynamicSecret',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.TextField()),
('description', models.TextField(blank=True)),
('path', models.TextField(default='/')),
('default_ttl', models.DurationField(help_text='Default TTL for leases (must be <= max_ttl).')),
('max_ttl', models.DurationField(help_text='Maximum allowed TTL for leases.')),
('provider', models.CharField(choices=[('aws', 'AWS')], help_text='Which provider this secret is associated with.', max_length=50)),
('config', models.JSONField()),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('authentication', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.providercredentials')),
('environment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.environment')),
('folder', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.secretfolder')),
],
),
migrations.CreateModel(
name='DynamicSecretLease',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.TextField()),
('description', models.TextField(blank=True)),
('ttl', models.DurationField()),
('status', models.CharField(choices=[('active', 'Active'), ('renewed', 'Renewed'), ('revoked', 'Revoked'), ('expired', 'Expired')], default='active', help_text='Current status of the lease', max_length=50)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('renewed_at', models.DateTimeField(null=True)),
('expires_at', models.DateTimeField(null=True)),
('revoked_at', models.DateTimeField(null=True)),
('deleted_at', models.DateTimeField(null=True)),
('organisation_member', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.organisationmember')),
('secret', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leases', to='api.dynamicsecret')),
('service_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.serviceaccount')),
],
),
migrations.CreateModel(
name='DynamicSecretLeaseEvent',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('lease', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.dynamicsecretlease')),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Generated by Django 4.2.22 on 2025-08-26 09:16

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

dependencies = [
('api', '0106_dynamicsecret_dynamicsecretlease_and_more'),
]

operations = [
migrations.AddField(
model_name='dynamicsecret',
name='key_map',
field=models.JSONField(default=list, help_text="Provider-agnostic mapping of keys: [{'id': '<key_id>', 'name': '<key_name>'}, ...]"),
),
migrations.AddField(
model_name='dynamicsecretlease',
name='credentials',
field=models.JSONField(default=list),
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='event_type',
field=models.CharField(choices=[('created', 'Created'), ('active', 'Active'), ('renewed', 'Renewed'), ('revoked', 'Revoked'), ('expired', 'Expired')], default='created', max_length=50),
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='ip_address',
field=models.GenericIPAddressField(blank=True, null=True),
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='metadata',
field=models.JSONField(blank=True, default=dict),
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='organisation_member',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lease_events', to='api.organisationmember'),
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='service_account',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lease_events', to='api.serviceaccount'),
),
migrations.AddField(
model_name='dynamicsecretleaseevent',
name='user_agent',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='dynamicsecretlease',
name='status',
field=models.CharField(choices=[('created', 'Created'), ('active', 'Active'), ('renewed', 'Renewed'), ('revoked', 'Revoked'), ('expired', 'Expired')], default='active', help_text='Current status of the lease', max_length=50),
),
migrations.AlterField(
model_name='dynamicsecretleaseevent',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='dynamicsecretleaseevent',
name='lease',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='api.dynamicsecretlease'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.22 on 2025-08-28 08:49

from django.db import migrations, models
import uuid


class Migration(migrations.Migration):

dependencies = [
('api', '0107_dynamicsecret_key_map_dynamicsecretlease_credentials_and_more'),
]

operations = [
migrations.AddField(
model_name='dynamicsecretlease',
name='cleanup_job_id',
field=models.TextField(default=uuid.uuid4),
),
migrations.AlterField(
model_name='dynamicsecretlease',
name='credentials',
field=models.JSONField(default=dict),
),
]
18 changes: 18 additions & 0 deletions backend/api/migrations/0109_alter_dynamicsecret_key_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.22 on 2025-09-12 05:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0108_dynamicsecretlease_cleanup_job_id_and_more'),
]

operations = [
migrations.AlterField(
model_name='dynamicsecret',
name='key_map',
field=models.JSONField(default=list, help_text="Provider-agnostic mapping of keys: [{'id': '<key_id>', 'key_name': '<encrypted_key_name>', 'key_digest': '<key_digest>'}, ...]"),
),
]
Loading